在.net c#4 </t>中出现ConcurrentStack <t>问题

时间:2011-09-08 09:37:22

标签: c# asp.net multithreading c#-4.0 concurrency

此类旨在获取网址列表,扫描它们,然后返回不起作用的列表。它使用多个线程来避免永远占用长列表。

我的问题是,即使我用一个在所有网址上都返回失败的测试函数替换网址的实际扫描,该类会返回可变数量的失败。

我假设我的问题出在ConcurrentStack.TryPop()或.Push()上,但是我不知道为什么我要找出原因。它们被认为是线程安全的,我也试过锁定,没有帮助。

任何人都能向我解释我做错了什么?我对多线程没有很多经验..

public class UrlValidator
{
    private const int MAX_THREADS = 10;
    private List<Thread> threads = new List<Thread>();
    private ConcurrentStack<string> errors = new ConcurrentStack<string>();
    private ConcurrentStack<string> queue = new ConcurrentStack<string>();

    public UrlValidator(List<string> urls)
    {
        queue.PushRange(urls.ToArray<string>());
    }

    public List<string> Start()
    {
        threads = new List<Thread>();

        while (threads.Count < MAX_THREADS && queue.Count > 0)
        {
            var t = new Thread(new ThreadStart(UrlWorker));
            threads.Add(t);
            t.Start();
        }

        while (queue.Count > 0) Thread.Sleep(1000);

        int runningThreads = 0;
        while (runningThreads > 0)
        {
            runningThreads = 0;
            foreach (Thread t in threads) if (t.ThreadState == ThreadState.Running) runningThreads++;
            Thread.Sleep(100);
        }

        return errors.ToList<string>();
    }

    private void UrlWorker()
    {
        while (queue.Count > 0)
        {
            try 
            {
                string url = "";
                if (!queue.TryPop(out url)) continue;
                if (TestFunc(url) != 200) errors.Push(url);
            }
            catch
            {
                break;
            }
        }
    }

    private int TestFunc(string url)
    {
        Thread.Sleep(new Random().Next(100));
        return -1;
    }
}

2 个答案:

答案 0 :(得分:2)

这是Task Parallel LibraryPLINQ(并行LINQ)非常擅长的东西。看看如果你让.NET做它的事情会有多容易的例子:

public IEnumerable<string> ProcessURLs(IEnumerable<string> URLs)
{
    return URLs.AsParallel()
        .WithDegreeOfParallelism(10)
        .Where(url => testURL(url));
}

private bool testURL(string URL)
{
    // some logic to determine true/false
    return false;
}

只要有可能,您应该让.NET提供的库执行所需的任何线程管理。一般来说,TPL对此非常有用,但由于您只是简单地转换了一个项目集合,因此PLINQ非常适合这种情况。您可以修改并行度(我建议将其设置为小于最大并发TCP连接数),并且可以像LINQ允许的那样添加多个条件。自动并行运行,并且不进行线程管理。

答案 1 :(得分:1)

您的问题与ConcurrentStack无关,而是与您检查正在运行的线程的循环有关:<​​/ p>

int runningThreads = 0;
while (runningThreads > 0)
{
    ...
}

条件立即是假的,所以你永远不会真正等待线程。反过来,这意味着errors将包含到目前为止运行的任何线程的错误。

但是,您的代码还有其他问题,但手动创建线程可能是最好的。由于您使用的是.NET 4.0,因此应使用任务或PLINQ进行异步处理。使用PLINQ,您的验证可以实现为:

public IEnumerable<string> Validate(IEnumerable<string> urls)
{ 
    return urls.AsParallel().Where(url => TestFunc(url) != 200);
}