|
|
|
|
|
|
C#里 Task.WhenAll 和 Parallel.ForEach 都是實現(xiàn)多個并發(fā)任務執(zhí)行的一種方式。但是有一些不同之處,這些差異指定了它們各自適合的使用位置。
在比較它們差異之前,讓我們先簡單了解一下 Task.WhenAll 和 Parallel.ForEach 的概述。
Task.WhenAll 方法
創(chuàng)建一個任務,該任務將在所有提供的任務完成后完成。
| WhenAll(IEnumerable<Task>) | 創(chuàng)建一個任務,該任務將在可枚舉集合中的所有Task對象完成時完成。 |
| WhenAll(Task[]) | 創(chuàng)建一個任務,該任務將在數(shù)組中的所有Task對象完成時完成。 |
| WhenAll<TResult> (IEnumerable<Task<TResult>>) | 創(chuàng)建一個任務,該任務將在可枚舉集合中的所有Task<TResult>對象完成時完成。 |
| WhenAll<TResult>(Task<TResult>[]) | 創(chuàng)建一個任務,該任務將在數(shù)組中的所有Task<TResult>對象完成時完成。 |
Parallel.ForEach 方法
執(zhí)行一個foreach(Visual Basic 中的 For Each )操作,其中迭代可以并行運行。
| ForEach<TSource,TLocal>(IEnumerable<TSource>, ParallelOptions, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) | 使用IEnumerable上的線程本地數(shù)據(jù)執(zhí)行foreach(Visual Basic 中的For Each)操作,其中迭代可以并行運行,可以配置循環(huán)選項,并且可以監(jiān)視和操作循環(huán)的狀態(tài)。 |
| ForEach<TSource,TLocal>(IEnumerable<TSource>, ParallelOptions, Func<TLocal>, Func<TSource,ParallelLoopState,Int64,TLocal,TLocal>, Action<TLocal>) | 在IEnumerable上使用線程本地數(shù)據(jù)和 64 位索引執(zhí)行foreach(Visual Basic 中的For Each)操作,其中迭代可以并行運行,可以配置循環(huán)選項,并且可以監(jiān)視和操作循環(huán)的狀態(tài)。 |
| ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) | 使用IEnumerable上的線程本地數(shù)據(jù)執(zhí)行foreach(在Visual Basic 中的For Each)操作,其中迭代可以并行運行,并且可以監(jiān)視和操作循環(huán)的狀態(tài)。 |
| ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,Int64,TLocal,TLocal>, Action<TLocal>) | 使用IEnumerable上的線程本地數(shù)據(jù)執(zhí)行foreach(Visual Basic 中的For Each)操作,其中迭代可以并行運行,并且可以監(jiān)視和操作循環(huán)的狀態(tài)。 |
Task.WhenAll與Parallel.ForEach的差異
我想從一個示例開始來描述Task.WhenAll與Parallel.ForEach的差異。假設(shè)我們有一個方法,該方法由一個 HTTP 調(diào)用和一個存儲在數(shù)據(jù)庫中的命令組成。我們想同時執(zhí)行這個請求的多個任務。
public async Task Foo(Request request)
{
var result = await httpService.Send(request);
await repository.Store(result);
}
因此,為了同時執(zhí)行 Foo 方法的多個調(diào)用,我們可以在 .Net Core 中使用這些方法:
Parallel.ForEach(requests, async request =>
{
using (var sendScope = service.CreateScope())
{
var callService = sendScope.ServiceProvider.GetRequiredService<ICallService>();
await callService.Foo(request);
}
});
此外,可以使用 Task.WhenAll 來執(zhí)行此操作:
var tasks = requests.Select(async request =>
{
using (var sendScope = service.CreateScope())
{
var callService = sendScope.ServiceProvider.GetRequiredService<ICallService>();
await callService.Call(request);
}
});
await Task.WhenAll(tasks);
我已經(jīng)在我的筆記本電腦上執(zhí)行了它們,我從不同的執(zhí)行中捕獲了這些結(jié)果:

左側(cè)每個數(shù)字 100–15000 顯示并發(fā)任務數(shù)
上圖顯示了 Foo 方法指定數(shù)量的并行任務執(zhí)行需要多長時間,以毫秒為單位。
上面的執(zhí)行結(jié)果有兩個重點:
Task.WhenAll 相比,Parallel.ForEach 的執(zhí)行時間更快。Parallel.ForEach遇到失敗狀態(tài)。結(jié)論
總而言之,雖然 Parallel.ForEach 具有更快的執(zhí)行時間,但 Task.WhenAll 具有更高的可擴展性。這意味著通過增加請求負載,Task.WhenAll 可以毫無故障地處理它。
不要混淆可擴展性和速度,雖然 Parallel.ForEach 在速度上更好,但與 Task.WhenAll 相比可擴展性較低,無法響應高負載的并發(fā)請求。
這個結(jié)果來自于 Task.WhenAll 旨在處理具有更高可擴展性的并發(fā) I/O 綁定任務,因為它使用異步非阻塞方式共享線程來處理并發(fā)請求。
但是,另一方面,Parallel 本身是同步的。因此,在 CPU 綁定邏輯中使用它以獲得更好的性能是有益的。
相關(guān)文章
