我有这样的方法:
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;
}
但现在我有一个错误:
在异步操作仍处于挂起状态时完成异步模块或处理程序。
答案 0 :(得分:62)
async
与ForEach
的效果不佳。特别是,您的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 ...