|
|
|
|
|
C#里 Task.WhenAll
和 Parallel.ForEach
都是實(shí)現(xiàn)多個(gè)并發(fā)任務(wù)執(zhí)行的一種方式。但是有一些不同之處,這些差異指定了它們各自適合的使用位置。
在比較它們差異之前,讓我們先簡(jiǎn)單了解一下 Task.WhenAll
和 Parallel.ForEach
的概述。
Task.WhenAll 方法
創(chuàng)建一個(gè)任務(wù),該任務(wù)將在所有提供的任務(wù)完成后完成。
WhenAll(IEnumerable<Task>) | 創(chuàng)建一個(gè)任務(wù),該任務(wù)將在可枚舉集合中的所有Task 對(duì)象完成時(shí)完成。 |
WhenAll(Task[]) | 創(chuàng)建一個(gè)任務(wù),該任務(wù)將在數(shù)組中的所有Task 對(duì)象完成時(shí)完成。 |
WhenAll<TResult> (IEnumerable<Task<TResult>>) | 創(chuàng)建一個(gè)任務(wù),該任務(wù)將在可枚舉集合中的所有Task<TResult> 對(duì)象完成時(shí)完成。 |
WhenAll<TResult>(Task<TResult>[]) | 創(chuàng)建一個(gè)任務(wù),該任務(wù)將在數(shù)組中的所有Task<TResult> 對(duì)象完成時(shí)完成。 |
Parallel.ForEach 方法
執(zhí)行一個(gè)foreach
(Visual Basic 中的 For Each
)操作,其中迭代可以并行運(yùn)行。
ForEach<TSource,TLocal>(IEnumerable<TSource>, ParallelOptions, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) | 使用IEnumerable上的線程本地?cái)?shù)據(jù)執(zhí)行foreach (Visual Basic 中的For Each )操作,其中迭代可以并行運(yùn)行,可以配置循環(huán)選項(xiàng),并且可以監(jiān)視和操作循環(huán)的狀態(tài)。 |
ForEach<TSource,TLocal>(IEnumerable<TSource>, ParallelOptions, Func<TLocal>, Func<TSource,ParallelLoopState,Int64,TLocal,TLocal>, Action<TLocal>) | 在IEnumerable上使用線程本地?cái)?shù)據(jù)和 64 位索引執(zhí)行foreach (Visual Basic 中的For Each )操作,其中迭代可以并行運(yùn)行,可以配置循環(huán)選項(xiàng),并且可以監(jiān)視和操作循環(huán)的狀態(tài)。 |
ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) | 使用IEnumerable上的線程本地?cái)?shù)據(jù)執(zhí)行foreach (在Visual Basic 中的For Each )操作,其中迭代可以并行運(yùn)行,并且可以監(jiān)視和操作循環(huán)的狀態(tài)。 |
ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,Int64,TLocal,TLocal>, Action<TLocal>) | 使用IEnumerable上的線程本地?cái)?shù)據(jù)執(zhí)行foreach (Visual Basic 中的For Each )操作,其中迭代可以并行運(yùn)行,并且可以監(jiān)視和操作循環(huán)的狀態(tài)。 |
Task.WhenAll與Parallel.ForEach的差異
我想從一個(gè)示例開(kāi)始來(lái)描述Task.WhenAll
與Parallel.ForEach
的差異。假設(shè)我們有一個(gè)方法,該方法由一個(gè) HTTP 調(diào)用和一個(gè)存儲(chǔ)在數(shù)據(jù)庫(kù)中的命令組成。我們想同時(shí)執(zhí)行這個(gè)請(qǐng)求的多個(gè)任務(wù)。
public async Task Foo(Request request)
{
var result = await httpService.Send(request);
await repository.Store(result);
}
因此,為了同時(shí)執(zhí)行 Foo
方法的多個(gè)調(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
來(lái)執(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è)每個(gè)數(shù)字 100–15000 顯示并發(fā)任務(wù)數(shù)
上圖顯示了 Foo
方法指定數(shù)量的并行任務(wù)執(zhí)行需要多長(zhǎng)時(shí)間,以毫秒為單位。
上面的執(zhí)行結(jié)果有兩個(gè)重點(diǎn):
Task.WhenAll
相比,Parallel.ForEach
的執(zhí)行時(shí)間更快。Parallel.ForEach
遇到失敗狀態(tài)。結(jié)論
總而言之,雖然 Parallel.ForEach
具有更快的執(zhí)行時(shí)間,但 Task.WhenAll
具有更高的可擴(kuò)展性。這意味著通過(guò)增加請(qǐng)求負(fù)載,Task.WhenAll
可以毫無(wú)故障地處理它。
不要混淆可擴(kuò)展性和速度,雖然 Parallel.ForEach
在速度上更好,但與 Task.WhenAll
相比可擴(kuò)展性較低,無(wú)法響應(yīng)高負(fù)載的并發(fā)請(qǐng)求。
這個(gè)結(jié)果來(lái)自于 Task.WhenAll
旨在處理具有更高可擴(kuò)展性的并發(fā) I/O 綁定任務(wù),因?yàn)樗褂卯惒椒亲枞绞焦蚕砭€程來(lái)處理并發(fā)請(qǐng)求。
但是,另一方面,Parallel
本身是同步的。因此,在 CPU 綁定邏輯中使用它以獲得更好的性能是有益的。
相關(guān)文章