并行。用于增加意外数量的线程

时间:2019-05-29 20:29:31

标签: c# .net task-parallel-library

考虑以下代码:

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()下面的代码中,返回一百万个网址。

enter image description here

enter image description here

2 个答案:

答案 0 :(得分:1)

您的代码示例在编写时并未考虑异步。您完全不需要Parallel.ForEachHttpClient.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();