Parallel.ForEach 和 async-await [重复]
Posted
技术标签:
【中文标题】Parallel.ForEach 和 async-await [重复]【英文标题】:Parallel.ForEach and async-await [duplicate] 【发布时间】:2014-04-17 15:35:45 【问题描述】:我有这样的方法:
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;
但现在我遇到了一个错误:
异步模块或处理程序已完成,而异步操作仍处于挂起状态。
【问题讨论】:
你在哪里得到这个错误?我假设这是一个例外,它是否发生在GetResult
内?
你的Model
实际上是一个视图模型,它实现了INotifyPropertyChanged并绑定到视图?
不,它不是视图模型,可能我必须更改名称。这只是一个简单的class
和一些道具
返回异常return result;
GetResult
会影响性能吗?
【参考方案1】:
async
不适用于ForEach
。特别是,您的 async
lambda 正在转换为 async void
方法。有许多reasons to avoid 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;
【讨论】:
为什么不应该在 ASP.NET 中使用 prallel? @DirkBoer:并行代码将显着降低 ASP.NET 的可扩展性,并干扰其线程池启发式算法。只有当您有可并行的 CPU 密集型工作要做并且确定您将只有少量并发用户时,它才有用。 如果我要处理大量的项目,这段代码会不会尝试同时启动所有项目,需要数百个线程?我想象将多线程级别限制在 CPU 内核数量附近会比尝试同时做所有事情更快,从而导致大量的任务切换开销。 @ygoe:“这段代码是否会尝试同时启动所有这些” 是的。 “需要数百个线程?” No. @jmath412:听起来像async
问题; Parallel.ForEach
不适用于 async
。新的Parallel.ForEachAsync
可以,或者Task.WhenAll
也应该可以工作。【参考方案2】:
或者,您可以使用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
是一个扩展方法。
【讨论】:
【参考方案3】:.NET 6 终于添加了Parallel.ForEachAsync,这是一种安排异步工作的方法,可以让您控制并行度:
var urlsToDownload = new []
"https://dotnet.microsoft.com",
"https://www.microsoft.com",
"https://twitter.com/shahabfar"
;
var client = new HttpClient();
var options = new ParallelOptions MaxDegreeOfParallelism = 2 ;
await Parallel.ForEachAsync(urlsToDownload, options, async (url, token) =>
var targetPath = Path.Combine(Path.GetTempPath(), "http_cache", url);
var response = await client.GetAsync(url, token);
// The request will be canceled in case of an error in another URL.
if (response.IsSuccessStatusCode)
using var target = File.OpenWrite(targetPath);
await response.Content.CopyToAsync(target);
);
【讨论】:
【参考方案4】:啊,好吧。我想我知道现在发生了什么。 async method =>
一个“async void”,即“即发即弃”(不推荐用于除事件处理程序之外的任何东西)。这意味着调用者无法知道它何时完成......因此,GetResult
在操作仍在运行时返回。尽管我的第一个答案的技术细节不正确,但这里的结果是相同的:当 ForEach
启动的操作仍在运行时,GetResult 正在返回。你唯一能做的就是在Process
上不要await
(这样lambda 就不再是async
)并等待Process
完成每次迭代。但是,这将使用至少一个线程池线程来执行此操作,从而稍微对池施加压力——可能会毫无意义地使用ForEach
。我根本不会使用 Parallel.ForEach...
【讨论】:
以上是关于Parallel.ForEach 和 async-await [重复]的主要内容,如果未能解决你的问题,请参考以下文章
何时使用 Parallel.ForEach,何时使用 PLINQ
Parallel.ForEach 与 Task.Run 和 Task.WhenAll
Parallel.Invoke 和 Parallel.ForEach 本质上是一样的吗?