使用Parallel.foreach时,http查询没有获得正确的并行度

时间:2016-01-12 18:27:06

标签: c# concurrency

我正在尝试同时执行2000个http查询。 使用此代码进行的测试(主要由响应服务器组成)在大约15秒内完成:

    public void testTasks()
    {
        var urls = new List<string>();
        urls.AddRange(createUrls());
        var start = DateTime.Now;
        ConcurrentQueue<string> contents = new ConcurrentQueue<string>();
        Task.WaitAll(urls.Select(url =>
        {
            var client = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
            return client.GetAsync(url).ContinueWith(response =>
            {
                try
                {
                    var content = response.Result.Content.ReadAsStringAsync().Result;
                    contents.Enqueue(content);
                }
                catch (Exception e)
                {
                }
            });
        }).ToArray());
        var end = DateTime.Now;
        var time = end - start;
        Console.WriteLine("Time spent in Tasks : " + time.TotalSeconds);
        Console.WriteLine("Queue size : " + contents.Count);
    }

现在我使用Parallel.foreach执行相同的测试,我的运行时间为1分18秒:

    public void testParallelForeach()
    {
        var urls = new List<string>();
        urls.AddRange(createUrls());
        var start = DateTime.Now;
        ConcurrentQueue<string> contents = new ConcurrentQueue<string>();
        Parallel.ForEach(urls, new ParallelOptions() { MaxDegreeOfParallelism = urls.Count }, url =>
        {
            var client = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
            try
            {
                string content = client.GetStringAsync(url).Result;
                contents.Enqueue(content);
            }
            catch (Exception e)
            {

            }
        });
        var end = DateTime.Now;
        var time = end - start;
        Console.WriteLine("Time spent in ParallelForeach : " + time.TotalSeconds);
        Console.WriteLine("Queue size : " + contents.Count);
    }

如您所见,我使用的MaxDegreeOfParallelism等于服务器数量。但它看起来还不够。

修改

所以我的问题是:

- 为什么我的表现会有这样的差异?

- 我们使用parallel.foreach达到相同的性能吗?

2 个答案:

答案 0 :(得分:3)

我想提供一个与Scott Chamberlain提供的角度略有不同的角度。无论您使用同步还是异步IO都无关紧要。重要的是使用有效的DOP(并行度)来发送HTTP请求。

不同的Web服务更喜欢不同的DOP,您需要凭经验确定最佳DOP。

CPU核心数与最佳IO DOP无关。只是确保理解这一点。

第一个解决方案是坏的,因为它选择DOP = urls.Count。第二个是坏的,因为它选择DOP是TPL决定的(它不是等于MaxDegreeOfParallelism = urls.Count,因为那是最大值)。这些值都不是最佳的。

  1. 使用一种众所周知的技术来实现具有精确DOP的并行异步foreach(例如http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx)。
  2. 通过实验确定最佳DOP。
  3. 这是一个非常简单的问题需要解决。您陷入了使用TPL内置设施解决问题的常见陷阱。 TPL缺乏必要的工具来妥善解决这个问题。

    另请注意,调用Result否定了异步IO的好处(这在这里非常有用)。请改用await

答案 1 :(得分:2)

  

- 为什么我的表现会有这样的差异?

因为使用Parallel.ForEach进行IO绑定工作没有意义。 Parallel.ForEach仅适用于CPU绑定工作。此外Parallel.ForEach还有一个&#34;提升&#34;效果,它不是从MaxDegreeOfParallelism开始,它从1个线程开始,然后继续添加线程,因为它检测到工作可以使用更多线程,直到它到达MaxDegreeOfParallelism(有一个原因,它被调用&#34 ; Max DegreeOfParallelism &#34; not&#34; DegreeOfParallelism &#34;)。使用IO绑定工作而不是CPU绑定工作会大大扰乱调度算法。

  

- 我们使用parallel.foreach达到相同的性能吗?

不,你不能因为你使用错误的工具来完成工作,不正确的方法总会胜过更正确的方法(你的第一种方法是这样)

然而,使用Async方法然后立即调用.Result仍然是一种错误的方法,最好的方法是使用正确的async / await(也使用Stopwatch而不是{ {1}}以确定经过的时间并处理您创建的一次性课程

DateTime