[英]Parallel.ForEach and async-await
我有這樣的方法:
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;
}
但現在我有一個錯誤:
異步模塊或處理程序已完成,而異步操作仍處於掛起狀態。
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;
}
或者,使用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
是一個擴展方法。
啊,好吧。 我想我知道現在發生了什么。 async method =>
一個“異步無效”,它是“即發即棄”(不推薦用於事件處理程序以外的任何東西)。 這意味着調用者無法知道它何時完成......因此, GetResult
在操作仍在運行時返回。 盡管我的第一個答案的技術細節不正確,但這里的結果是相同的:GetResult 正在返回,而ForEach
啟動的操作仍在運行。 您真正可以做的唯一一件事就是不要在Process
await
(這樣 lambda 不再是async
)並等待Process
完成每次迭代。 但是,這將使用至少一個線程池線程來執行此操作,從而略微強調池——可能使ForEach
使用毫無意義。 我根本不會使用 Parallel.ForEach ...
.NET 6 最終添加了 Parallel.ForEachAsync,這是一種調度異步工作的方法,可讓您控制並行度:
var urlsToDownload = new []
{
"https://dotnet.microsoft.com",
"https://www.microsoft.com",
"https://twitter.com/shahabfar"
};
var client = new HttpClient();
await Parallel.ForEachAsync(urlsToDownload, async (url, token) =>
{
var targetPath = Path.Combine(Path.GetTempPath(), "http_cache", url);
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
using var target = File.OpenWrite(targetPath);
await response.Content.CopyToAsync(target);
}
});
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.