Parallel.ForEach和async-await等。

问题描述 投票:41回答:3

我有这样的方法。

public async Task<MyResult> GetResult()
{
    MyResult result = new MyResult();

    foreach(var method in Methods)
    {
        string json = await Process(method);

        result.Prop1 = PopulateProp1(json);
        result.Prop2 = PopulateProp2(json);

    }

    return result;
}

后来我决定用 Parallel.ForEach:

public async Task<MyResult> GetResult()
{
    MyResult result = new MyResult();

    Parallel.ForEach(Methods, async method =>
    {
        string json = await Process(method);    

        result.Prop1 = PopulateProp1(json);
        result.Prop2 = PopulateProp2(json);
    });

    return result;
}

但现在我遇到了一个错误。

一个异步模块或处理程序完成了,而一个异步操作还在等待。

c# async-await task-parallel-library parallel.foreach
3个回答
67
投票

async 不太适用于 ForEach. 特别是,你的 async lambda正被转换为 async void 的方法。有一些 避免的原因 async void (正如我在MSDN的一篇文章中所描述的那样);其中之一是你无法轻松地检测到当 async lambda已经完成。ASP.NET将看到您的代码在没有完成的情况下返回。async void 方法并(适当地)抛出一个异常。

你可能想要做的是处理数据 同时只是不在 平行. 并行代码几乎不应该在ASP.NET上使用。下面是异步并发处理时的代码。

public async Task<MyResult> GetResult()
{
  MyResult result = new MyResult();

  var tasks = Methods.Select(method => ProcessAsync(method)).ToArray();
  string[] json = await Task.WhenAll(tasks);

  result.Prop1 = PopulateProp1(json[0]);
  ...

  return result;
}

9
投票

另外,如果使用 AsyncEnumerator NuGet包。 你可以这样做。

using System.Collections.Async;

public async Task<MyResult> GetResult()
{
    MyResult result = new MyResult();

    await Methods.ParallelForEachAsync(async method =>
    {
        string json = await Process(method);    

        result.Prop1 = PopulateProp1(json);
        result.Prop2 = PopulateProp2(json);
    }, maxDegreeOfParallelism: 10);

    return result;
}

哪儿 ParallelForEachAsync 是一个扩展方法。


5
投票

Ahh, okay. 我想我现在知道是怎么回事了。 async method => 是一个 "async void",是 "fire and forget"(除了事件处理程序外,不建议用于其他任何事情)。 这意味着调用者无法知道它何时完成... ... 所以。GetResult 返回的同时,操作仍在运行。 虽然我的第一个答案的技术细节是不正确的,但这里的结果是一样的:GetResult在返回的同时,由 ForEach 还在运行。 你唯一能做的就是不 await 关于 Process (这样,lambda就不再是......) async)并等待 Process 来完成每次迭代。 但是,这将使用至少一个线程池线程来做这件事,因此会给线程池带来轻微的压力--可能会利用 ForEach 毫无意义。 我干脆不使用Parallel.ForEach...

© www.soinside.com 2019 - 2024. All rights reserved.