繁体   English   中英

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

[英]Parallelizing a task using .AsParallel().ForAll or Parallel.ForEach performance issue

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

我有这个动作

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分钟来完成其余的工作。

在连接线程时,这看起来像是一个问题,但我无法发现这可能是什么。

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

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

您等待免费代理的方式也很浪费。 与其使用while (ProxyList.Count == 0)在没有免费代理的情况下冻结线程的情况, while (ProxyList.Count == 0)考虑使用计时器,它会每秒唤醒一次,并检查免费代理是否可用。 它不是最佳解决方案,但至少不会浪费线程。 更好的解决方案是向ProxyHandler添加事件,该事件将在代理可用时通知。

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

如果使用Range Partitiner,则您的URL集合将分为几组,每组中的URL数目相等。 然后,为每个组启动任务,而无需进一步同步。

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

解决方案是使用其他分区程序。 您可以按照MSDN上的说明使用内置的Chunk Partitioner。

如果这样做还不够好,您将不得不编写/查找一个分区生成器,该生成器会逐个生成元素。 这内置于C#5: EnumerablePartitionerOptions

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM