Parallel.ForEach和async-await

时间:2014-04-17 15:35:45

标签: c# async-await task-parallel-library parallel.foreach

我有这样的方法:

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;
}

但现在我有一个错误:

  

在异步操作仍处于挂起状态时完成异步模块或处理程序。

3 个答案:

答案 0 :(得分:62)

asyncForEach的效果不佳。特别是,您的async lambda正在转换为async void方法。有一些reasons to avoid async void(正如我在MSDN文章中描述的那样);其中之一就是你无法轻易检测到async lambda何时完成。 ASP.NET将在不完成async void方法的情况下看到您的代码返回,并(适当地)抛出异常。

您可能想要做的是同时处理数据 ,而不是 parallel 。几乎不应该在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;
}

答案 1 :(得分:6)

或者,使用AsyncEnumerator NuGet Package,您可以执行此操作:

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是一种扩展方法。

答案 2 :(得分:5)

啊,好的。我想我知道现在发生了什么。 async method =>&#34; async void&#34;这是&#34;火与忘记&#34; (不推荐用于事件处理程序以外的任何其他内容)。这意味着调用者无法知道它何时完成...因此,GetResult在操作仍在运行时返回。虽然我的第一个答案的技术细节不正确,但结果在这里是相同的:当ForEach开始的操作仍在运行时,GetResult正在返回。你唯一能做的就是await上不Process(所以lambda不再是async)并等待Process完成每次迭代。但是,这将使用至少一个线程池线程来做到这一点,从而稍微强调池 - 可能使用ForEach毫无意义。我不会使用Parallel.ForEach ...