我正在尝试同时执行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达到相同的性能吗?
答案 0 :(得分:3)
我想提供一个与Scott Chamberlain提供的角度略有不同的角度。无论您使用同步还是异步IO都无关紧要。重要的是使用有效的DOP(并行度)来发送HTTP请求。
不同的Web服务更喜欢不同的DOP,您需要凭经验确定最佳DOP。
CPU核心数与最佳IO DOP无关。只是确保理解这一点。
第一个解决方案是坏的,因为它选择DOP = urls.Count。第二个是坏的,因为它选择DOP是TPL决定的(它不是不等于MaxDegreeOfParallelism = urls.Count
,因为那是最大值)。这些值都不是最佳的。
这是一个非常简单的问题需要解决。您陷入了使用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