使用.AsParallel()。ForAll或Parallel.ForEach性能问题并行化任务

时间:2011-12-07 13:23:00

标签: c# multithreading c#-4.0 task-parallel-library

我有一个网站列表和一个代理服务器列表。

我有这个动作

Action<string> action = (string url) =>
{
    var proxy = ProxyHandler.GetProxy();
    HtmlDocument html = null;
    while (html == null)
    {
        try
        {

            html = htmlDocumentLoader.LoadDocument(url, proxy.Address);

            // Various db manipulation code

            ProxyHandler.ReleaseProxy(proxy);
        }
        catch (Exception exc)
        {
            Console.WriteLine("{0} proxies remain", ProxyHandler.ListSize());

            // Various db manipulation code

            proxy = ProxyHandler.GetProxy();
        }
    }
};

我打电话使用

urlList.AsParallel().WithDegreeOfParallelism(12).ForAll(action);

Parallel.ForEach(urlList, action);

我的ProxyHandler类如下

public static class ProxyHandler
{    
    static List<Proxy> ProxyList = new ProxyRepository().GetAliveProxies().ToList();

    public static Proxy GetProxy()
    {
        lock (ProxyList)
        {
            while (ProxyList.Count == 0)
            {
                Console.WriteLine("Sleeping");
                Thread.Sleep(1000);
            }
            var proxy = ProxyList[0];
            ProxyList.RemoveAt(0);
            return proxy;
        }           
    }

    public static void ReleaseProxy(Proxy proxy)
    {
        lock (ProxyList)
        {
            if(!ProxyList.Contains(proxy))ProxyList.Add(proxy);
        }
    }

    public static int ListSize()
    {
        lock (ProxyList)
        {
            return ProxyList.Count;
        }
    }
}

我的问题是,当执行此操作时,它似乎可以很快完成约90%的网站,然后花费很长时间来完成其余的工作。

我的意思是,在100个网址中,花费尽可能多的时间来完成前90个,就像它在过去10个时一样。

我已经排除代理已经死亡,因为没有抛出任何异常。看起来好像urlList上的最后一个项目需要很长时间才能完成。

更新

我正在添加一些运行数据,以使我的问题更加清晰:

Minute    1 2   3   4   5   6   7   8   9   16  18  19
Count    23 32  32  17  6   1   1   1   1   2   1   2

正如你在前4分钟看到的,我做了104/119的请求。其余的需要15分钟。

这似乎是加入Threads时的一个问题,但我没有发现它可能是什么。

2 个答案:

答案 0 :(得分:3)

你在浪费线程和CPU时间。在这种情况下,你将有12个线程;每个线程一次只处理一个url。因此,您一次只能处理12个网址。而且,大多数情况下,这些线程不会做任何事情(它们只是等待一个免费代理或一个加载的页面),而它们可以用于更有用的任务。

为避免这种情况,您应该使用非阻塞IO操作。因此,您应该考虑使用其异步接口之一(htmlDocumentLoader.LoadDocument / htmlDocumentLoader.BeginLoadDocumenthtmlDocumentLoader.EndLoadDocument / htmlDocumentLoader.LoadDocumentAsync),而不是使用htmlDocumentLoader.LoadDocumentCompleted。在这种情况下,如果你有100个url,它们将同时加载而不会创建额外的线程并浪费CPU时间。只有在加载页面时,才会创建新线程(实际上是从ThreadPool中获取)来处理它。

等待免费代理的方式也很浪费。如果没有免费代理,请使用冻结线程的while (ProxyList.Count == 0),而不是使用定时器,它会每秒唤醒并检查免费代理是否可用。它不是最好的解决方案,但至少它不会浪费线程。更好的解决方案是向ProxyHandler添加一个事件,该事件将在代理可用时通知。

答案 1 :(得分:2)

您的问题可能是由于PLinq使用了分区程序。

如果正在使用Range Partitiner,则您的url集合将被拆分为每组中具有相等(ish)数量的url的组。然后为每个组启动任务,没有进一步的同步。

这意味着将有一个任务花费最长时间,并且在所有其他任务完成后仍有工作要做。这实际上意味着操作的最后一部分是单线程的。

解决方案是使用不同的分区程序。您可以使用内置的Chunk Partitioner,如MSDN所述。

如果这种方法不能很好地工作,则必须编写/找到一个逐个生成元素的分区器实现。这内置于C#5:EnumerablePartitionerOptions