实例显示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 绑定逻辑中使用它以获得更好的性能是有益的。
相关文章
- 站长推荐