涉及异步操作时,Parallel.ForEach 或 Task.WhenAll ?

Sup*_*Bob 4 c# concurrency asynchronous async-await parallel.foreach

我已阅读以下密切相关的主题,但我想问一个更具体的事情。

如果我们需要异步运行任务/方法,并且这些任务本身运行其他任务/等待其他任务,则首选哪种变体 - Parallel.ForEach,或Task.WhenAll?我将用下面的一些代码进行演示:

public async Task SomeWorker(string param1, HttpClient client,
    List<FillMeUp> emptyCollection)
{
    HttpRequestMessage message = new HttpRequestMessage();
    message.Method = HttpMethod.Get;
    message.Headers.Add("someParam", param1);
    message.RequestUri = new Uri("https://www.somesite.me");
    var requestResponse = await client.SendAsync(message).ConfigureAwait(false);
    var content = await requestResponse.Content.ReadAsStringAsync()
        .ConfigureAwait(false);
    emptyCollection.Add(new FillMeUp()
    {
        Param1 = param1
    });
}
Run Code Online (Sandbox Code Playgroud)

与 一起使用WhenAll

using (HttpClient client = new HttpClient())
{
    client.DefaultRequestHeaders.Add("Accept", "application/json");

    List<FullCollection> fullCollection = GetMyFullCollection();
    List<FillMeUp> emptyCollection = new List<FillMeUp>();
    List<Task> workers = new List<Task>();
    for (int i = 0; i < fullCollection.Count; i++)
    {
        workers.Add(SomeWorker(fullCollection[i].SomeParam, client,
            emptyCollection));
    }

    await Task.WhenAll(workers).ConfigureAwait(false);

    // Do something below with already completed tasks
}
Run Code Online (Sandbox Code Playgroud)

或者,将以上所有内容写在Parallel.ForEach()

using (HttpClient client = new HttpClient())
{
    client.DefaultRequestHeaders.Add("Accept", "application/json");

    List<FullCollection> fullCollection = GetMyFullCollection();
    List<FillMeUp> emptyCollection = new List<FillMeUp>();
    Parallel.ForEach<FullCollection>(fullCollection, (fullObject) =>
    {
       HttpRequestMessage message = new HttpRequestMessage();
       message.Method = HttpMethod.Get;
       message.Headers.Add("someParam", fullObject.SomeParam);
       message.RequestUri = new Uri("https://www.somesite.me");
       var requestResponse = client.SendAsync(message)
           .GetAwaiter().GetResult();
       var content = requestResponse.Content.ReadAsStringAsync()
           .GetAwaiter().GetResult();
       emptyCollection.Add(new FillMeUp()
       {
          Param1 = fullObject.SomeParam
       });
    });
}
Run Code Online (Sandbox Code Playgroud)

我知道列表不是线程安全的。这只是为了证明我的问题的性质。

HttpClient(SendAsync和)的两个方法ReadAsStringAsync都是异步的,因此必须同步调用才能使用Parallel.ForEach

这比Task.WhenAll路线更受欢迎吗?我尝试了各种性能测试,但似乎找不到差异。

The*_*ias 8

一般来说,Parallel当您的 CPU 有大量工作要做时,您就需要该类。Task当您必须等待外部世界为您完成大量工作时,您就需要该课程。

  • Parallel= 计算。(CPU 限制
  • Task= 等待网络服务器、文件系统和数据库响应。(I/O 限制)

从.NET 6开始,Parallel现在配备了异步ForEachAsync方法,它接受异步body委托,并支持与同步方法相同的选项Parallel。最重要的选项是MaxDegreeOfParallelism,因为当请求溢出时,Web 服务器、文件系统和数据库都会表现得很糟糕。在此之前,我们必须求助于TPL Dataflow库等高级工具,或者编写自定义ForEachAsync实现,如本问题所示。

应该注意的是,Tasks 也可用于 CPU 密集型工作。该类在内部Parallel使用Tasks 作为构建块。这种关系通过总称任务并行库(TPL) 可以明显看出。