foreach和Task.WhenAll()有执行上的区别吗?

7
在我的应用程序管道中有一个选项,可以注入一组预请求处理程序,这些处理程序可以在请求首次进入管道时改变请求。管道是完全异步的,因此这些预请求处理程序调用(以及其他所有内容)都必须等待。我有几种不同的调用这些处理程序的方式,我想知道它们之间是否有任何区别,如果有,那么区别是什么?例如,各种处理程序是否按相同顺序调用?哪个选项可能提供最佳性能?
选项1: foreach
foreach (var handler in this.preRequestHandlers)
{
    await handler.Handle(request);
}

选项2:ForEach()
this.preRequestHandlers.ForEach(async handler => await handler.Handle(request));

Option 3: Task.WhenAll()

await Task.WhenAll(this.preRequestHandlers.Select(handler => handler.Handle(request)));

Task.WhenAll 方法 会等待所有任务结束,并在不同的线程中运行,可能是并发的。而你的 foreach 循环则是按顺序一个接一个地运行。 - rmjoia
1
@rmjoia 这段代码并没有创建任何额外的线程。所有这些解决方案都使用了完全相同数量的线程(即调用线程以及handler.Handle创建的线程数,可能是零)。 - Servy
2个回答

8
假设同时运行各个处理程序会带来收益(您将知道这是否正确),则选项3是最佳选择,因为它将在几乎零的时间内创建任务集合,然后潜在地并行执行,然后等待所有任务完成。选项1将按顺序依次执行每个处理程序。因此,如果每个处理程序需要4秒钟,同时运行多个仍需要约4秒钟,则选项3将在约4秒钟内完成。选项1将在4 x(预请求处理程序数量)秒内完成。
如果您需要按顺序执行处理程序,一个接一个地执行,每次只执行一个处理程序,则选项3不适用。
选项2实际上对任何事情都不太适用,因为它将在处理程序任务完成之前完成。原因请参见现有的SO答案之一,例如: 如何使用Async和ForEach? C# async await using LINQ ForEach()

使用异步的 List<T>.ForEach() 有什么意义吗?


1

前两个选项将启动并等待当前线程中的所有任务,而第三个选项生成一个单独的任务(其中包含等待任务),该任务从当前线程等待。

第二个选项调用了另一个函数,该函数对每个元素调用另一个函数(lambda),因此它比选项1多了一个开销。

在第三个选项中,原始集合被投影到另一个集合中(使用Select),这会导致速度比前两个选项慢。


所以我认为最好的方法是选项1,它可以减少任务创建和函数堆叠的开销。

作为第一种选项的性能改进,我建议您使用for循环而不是foreach这里有一个很好的解释)。


1
这三种方法的行为方式截然不同,不仅在性能上有所差异,而且在实际执行和可观察结果方面也有所不同。因此,这个答案非常错误和非常危险。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接