考虑以下代码:
var options = new ParallelOptions();
var urls = GetListOfUrls();
Parallel.ForEach(urls, options, url => {
try {
using (HttpClient client = new HttpClient()) {
client.Timeout = TimeSpan.FromMinutes(30);
Task task = client.GetAsync(url);
task.Wait();
}
} catch (Exception exception) {
Console.WriteLine(exception.Message);
}
});
在具有8个内核的VM上,它最多可旋转670个线程。这正常吗?我的理解是,TPL的经验法则是每个内核25个线程,这将使它进入200个线程范围。
P.S。在GetListOfUrls()
下面的代码中,返回一百万个网址。
答案 0 :(得分:1)
您的代码示例在编写时并未考虑异步。您完全不需要Parallel.ForEach
。 HttpClient.GetAsync
已经异步,将它包装在 CPU绑定的任务中没有意义。
private readonly _httpClient = new HttpClient();
var tasks = new List<Task>();
foreach(var url in urls)
{
var task = DoWork(url);
tasks.Add(task);
}
await Task.WhenAll(tasks);
foreach(var task in tasks)
{
if (task.Exception != null)
Console.WriteLine(task.Exception.Message);
}
public async Task DoWork(string url)
{
var json = await _httpClient.GetAsync(url);
// do something with json
}
尽管Parallel.ForEach()
是循环的一种更有效的版本,但使用Task.Run()
时,它实际上仅应用于Cpu Bound work (Task.Run Etiquette and Proper Usage)。调用URL并不是CPU的工作,而是I / O的工作(或更确切地说,称为IO Completion Port work)。
YOU'RE USING HTTPCLIENT WRONG AND IT IS DESTABILIZING YOUR SOFTWARE
感谢HttpClient链接。哇。该修补程序似乎是一种反模式。
虽然看起来像是反模式,但这是因为提供的解决方案实际上是反模式。 HttpClient
正在使用外部资源来完成其工作,因此应将其处置(它实现IDisposable
),但同时应将其用作单例。这带来了问题,因为没有干净的方法来处理用作类的静态属性/字段的单例。但是,由于它是由mirosoft编写的,因此,如果文档中另有说明,我们不必总是处置我们创建的对象。
由于您指出Parallel.ForEach和Task.Run都不适合HttpClient工作,因为它受I / O约束,因此您会推荐什么?
异步/等待
我已经在问题中添加了百万部分
因此,您需要限制并行任务的数量,以便:
var maximumNumberofParallelOperations = 1;
foreach(var url in urls)
{
var task = DoWork(url);
tasks.Add(task);
while (allTasks.Count(t => !t.IsCompleted) >= maximumNumberofParallelOperations )
{
await Task.WhenAny(allTasks);
}
}
答案 1 :(得分:0)
在这里并行是多余的,因为HttpClient方法是异步的。我建议改为异步调用它们。否则,您将创建大量线程,这些线程除了等待之外什么都不做。
此外,使用HttpClient
的单个实例。这将增加缓存命中率,并减少HttpClient加速线程访问代理或DNS信息的需要。
var client = new HttpClient();
var tasks = urls.Select( url => client.GetAsync(url) ).ToList();
await Task.WhenAll(tasks);
var results = tasks.Select( task => task.Result ).ToList();