实例显示C# Task.WhenAll与Parallel.ForEach的使用差异
作者:admin 时间:2023-6-7 11:42:11 浏览:C#里 Task.WhenAll 和 Parallel.ForEach 都是实现多个并发任务执行的一种方式。但是有一些不同之处,这些差异指定了它们各自适合的使用位置。
在比较它们差异之前,让我们先简单了解一下 Task.WhenAll 和 Parallel.ForEach 的概述。
Task.WhenAll 方法
定义
- 命名空间:System.Threading.Tasks
- 集合:System.Runtime.dll
创建一个任务,该任务将在所有提供的任务完成后完成。
重载
| WhenAll(IEnumerable<Task>) | 创建一个任务,该任务将在可枚举集合中的所有Task对象完成时完成。 |
| WhenAll(Task[]) | 创建一个任务,该任务将在数组中的所有Task对象完成时完成。 |
| WhenAll<TResult> (IEnumerable<Task<TResult>>) | 创建一个任务,该任务将在可枚举集合中的所有Task<TResult>对象完成时完成。 |
| WhenAll<TResult>(Task<TResult>[]) | 创建一个任务,该任务将在数组中的所有Task<TResult>对象完成时完成。 |
Parallel.ForEach 方法
定义
- 命名空间:System.Threading.Tasks
- 集合:System.Threading.Tasks.Parallel.dll
执行一个foreach(Visual Basic 中的 For Each )操作,其中迭代可以并行运行。
重载
| ForEach<TSource,TLocal>(IEnumerable<TSource>, ParallelOptions, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) | 使用IEnumerable上的线程本地数据执行foreach(Visual Basic 中的For Each)操作,其中迭代可以并行运行,可以配置循环选项,并且可以监视和操作循环的状态。 |
| ForEach<TSource,TLocal>(IEnumerable<TSource>, ParallelOptions, Func<TLocal>, Func<TSource,ParallelLoopState,Int64,TLocal,TLocal>, Action<TLocal>) | 在IEnumerable上使用线程本地数据和 64 位索引执行foreach(Visual Basic 中的For Each)操作,其中迭代可以并行运行,可以配置循环选项,并且可以监视和操作循环的状态。 |
| ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) | 使用IEnumerable上的线程本地数据执行foreach(在Visual Basic 中的For Each)操作,其中迭代可以并行运行,并且可以监视和操作循环的状态。 |
| ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>, Func<TSource,ParallelLoopState,Int64,TLocal,TLocal>, Action<TLocal>) | 使用IEnumerable上的线程本地数据执行foreach(Visual Basic 中的For Each)操作,其中迭代可以并行运行,并且可以监视和操作循环的状态。 |
Task.WhenAll与Parallel.ForEach的差异
我想从一个示例开始来描述Task.WhenAll与Parallel.ForEach的差异。假设我们有一个方法,该方法由一个 HTTP 调用和一个存储在数据库中的命令组成。我们想同时执行这个请求的多个任务。
public async Task Foo(Request request)
{
var result = await httpService.Send(request);
await repository.Store(result);
}
因此,为了同时执行 Foo 方法的多个调用,我们可以在 .Net Core 中使用这些方法:
Parallel.ForEach(requests, async request =>
{
using (var sendScope = service.CreateScope())
{
var callService = sendScope.ServiceProvider.GetRequiredService<ICallService>();
await callService.Foo(request);
}
});
此外,可以使用 Task.WhenAll 来执行此操作:
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);
我已经在我的笔记本电脑上执行了它们,我从不同的执行中捕获了这些结果:

左侧每个数字 100–15000 显示并发任务数
上图显示了 Foo 方法指定数量的并行任务执行需要多长时间,以毫秒为单位。
上面的执行结果有两个重点:
- 首先,与
Task.WhenAll相比,Parallel.ForEach的执行时间更快。 - 第二点是当并发任务超过8000时,
Parallel.ForEach遇到失败状态。
结论
总而言之,虽然 Parallel.ForEach 具有更快的执行时间,但 Task.WhenAll 具有更高的可扩展性。这意味着通过增加请求负载,Task.WhenAll 可以毫无故障地处理它。
不要混淆可扩展性和速度,虽然 Parallel.ForEach 在速度上更好,但与 Task.WhenAll 相比可扩展性较低,无法响应高负载的并发请求。
这个结果来自于 Task.WhenAll 旨在处理具有更高可扩展性的并发 I/O 绑定任务,因为它使用异步非阻塞方式共享线程来处理并发请求。
但是,另一方面,Parallel 本身是同步的。因此,在 CPU 绑定逻辑中使用它以获得更好的性能是有益的。
相关文章



