C# 为什么我应该更喜欢单个“await Task.WhenAll”而不是多个等待?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18310996/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why should I prefer single 'await Task.WhenAll' over multiple awaits?
提问by avo
In case I do not care about the order of task completion and just need them all to complete, should I still use await Task.WhenAll
instead of multiple await
? e.g, is DoWork2
below a preferred method to DoWork1
(and why?):
如果我不关心任务完成的顺序而只需要它们全部完成,我是否仍然应该使用await Task.WhenAll
而不是多个await
?例如,DoWork2
低于DoWork1
(以及为什么?)的首选方法:
using System;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
static async Task<string> DoTaskAsync(string name, int timeout)
{
var start = DateTime.Now;
Console.WriteLine("Enter {0}, {1}", name, timeout);
await Task.Delay(timeout);
Console.WriteLine("Exit {0}, {1}", name, (DateTime.Now - start).TotalMilliseconds);
return name;
}
static async Task DoWork1()
{
var t1 = DoTaskAsync("t1.1", 3000);
var t2 = DoTaskAsync("t1.2", 2000);
var t3 = DoTaskAsync("t1.3", 1000);
await t1; await t2; await t3;
Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
static async Task DoWork2()
{
var t1 = DoTaskAsync("t2.1", 3000);
var t2 = DoTaskAsync("t2.2", 2000);
var t3 = DoTaskAsync("t2.3", 1000);
await Task.WhenAll(t1, t2, t3);
Console.WriteLine("DoWork2 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
static void Main(string[] args)
{
Task.WhenAll(DoWork1(), DoWork2()).Wait();
}
}
}
采纳答案by usr
Yes, use WhenAll
because it propagates all errors at once. With the multiple awaits, you lose errors if one of the earlier awaits throws.
是的,使用WhenAll
因为它一次传播所有错误。使用多个等待,如果较早的等待之一抛出,您将丢失错误。
Another important difference is that WhenAll
will wait for all tasks to complete even in the presence of failures(faulted or canceled tasks). Awaiting manually in sequence would cause unexpected concurrency because the part of your program that wants to wait will actually continue early.
另一个重要的区别是,即使出现故障(故障或取消的任务),WhenAll
也会等待所有任务完成。按顺序手动等待会导致意外并发,因为您的程序中想要等待的部分实际上会提前继续。
I think it also makes reading the code easier because the semantics that you want are directly documented in code.
我认为这也使阅读代码更容易,因为您想要的语义直接记录在代码中。
回答by Marcel Popescu
My understanding is that the main reason to prefer Task.WhenAll
to multiple await
s is performance / task "churning": the DoWork1
method does something like this:
我的理解是,更喜欢Task.WhenAll
多个await
s 的主要原因是性能/任务“搅动”:该DoWork1
方法执行以下操作:
- start with a given context
- save the context
- wait for t1
- restore the original context
- save the context
- wait for t2
- restore the original context
- save the context
- wait for t3
- restore the original context
- 从给定的上下文开始
- 保存上下文
- 等待t1
- 还原原始上下文
- 保存上下文
- 等待t2
- 还原原始上下文
- 保存上下文
- 等待t3
- 还原原始上下文
By contrast, DoWork2
does this:
相比之下,DoWork2
这样做:
- start with a given context
- save the context
- wait for all of t1, t2 and t3
- restore the original context
- 从给定的上下文开始
- 保存上下文
- 等待所有 t1、t2 和 t3
- 还原原始上下文
Whether this is a big enough deal for your particular case is, of course, "context-dependent" (pardon the pun).
对于您的特定情况,这是否足够大,当然是“依赖上下文”(请原谅双关语)。
回答by Lukazoid
An asynchronous method is implemented as a state-machine. It is possible to write methods so that they are not compiled into state-machines, this is often referred to as a fast-track async method. These can be implemented like so:
异步方法被实现为状态机。可以编写方法以便它们不被编译成状态机,这通常被称为快速跟踪异步方法。这些可以像这样实现:
public Task DoSomethingAsync()
{
return DoSomethingElseAsync();
}
When using Task.WhenAll
it is possible to maintain this fast-track code while still ensuring the caller is able to wait for all tasks to be completed, e.g.:
使用Task.WhenAll
时可以维护这个快速跟踪代码,同时仍然确保调用者能够等待所有任务完成,例如:
public Task DoSomethingAsync()
{
var t1 = DoTaskAsync("t2.1", 3000);
var t2 = DoTaskAsync("t2.2", 2000);
var t3 = DoTaskAsync("t2.3", 1000);
return Task.WhenAll(t1, t2, t3);
}
回答by rarrarrarrr
The other answers to this question offer up technical reasons why await Task.WhenAll(t1, t2, t3);
is preferred. This answer will aim to look at it from a softer side (which @usr alludes to) while still coming to the same conclusion.
这个问题的其他答案提供了await Task.WhenAll(t1, t2, t3);
首选的技术原因。这个答案旨在从更温和的方面(@usr 暗示)来看待它,同时仍然得出相同的结论。
await Task.WhenAll(t1, t2, t3);
is a more functional approach, as it declares intent and is atomic.
await Task.WhenAll(t1, t2, t3);
是一种更实用的方法,因为它声明了意图并且是原子的。
With await t1; await t2; await t3;
, there is nothing preventing a teammate (or maybe even your future self!) from adding code between the individual await
statements. Sure, you've compressed it to one line to essentially accomplish that, but that doesn't solve the problem. Besides, it's generally bad form in a team setting to include multiple statements on a given line of code, as it can make the source file harder for human eyes to scan.
使用await t1; await t2; await t3;
,没有什么可以阻止队友(甚至可能是您未来的自己!)在各个await
语句之间添加代码。当然,您已将其压缩为一行以基本实现这一点,但这并不能解决问题。此外,在团队设置中,在给定的代码行中包含多个语句通常是不好的形式,因为它会使人眼更难扫描源文件。
Simply put, await Task.WhenAll(t1, t2, t3);
is more maintainable, as it communicates your intent more clearly and is less vulnerable to peculiar bugs that can come out of well-meaning updates to the code, or even just merges gone wrong.
简而言之,await Task.WhenAll(t1, t2, t3);
它更易于维护,因为它更清楚地传达了您的意图,并且不易受到特殊错误的影响,这些错误可能来自对代码的善意更新,甚至只是合并出错。
回答by David Refaeli
(Disclaimer: This answer is taken/inspired from Ian Griffiths' TPL Async course on Pluralsight)
(免责声明:此答案取自 Ian Griffiths 在Pluralsight上的 TPL Async 课程)
Another reason to prefer WhenAll is Exception handling.
另一个喜欢 WhenAll 的原因是异常处理。
Suppose you had a try-catch block on your DoWork methods, and suppose they were calling different DoTask methods:
假设您在 DoWork 方法上有一个 try-catch 块,并假设它们正在调用不同的 DoTask 方法:
static async Task DoWork1() // modified with try-catch
{
try
{
var t1 = DoTask1Async("t1.1", 3000);
var t2 = DoTask2Async("t1.2", 2000);
var t3 = DoTask3Async("t1.3", 1000);
await t1; await t2; await t3;
Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
catch (Exception x)
{
// ...
}
}
In this case, if all 3 tasks throw exceptions, only the first one will be caught. Any later exception will be lost. I.e. if t2 and t3 throws exception, only t2 will be catched; etc. The subsequent tasks exceptions will go unobserved.
在这种情况下,如果所有 3 个任务都抛出异常,则只会捕获第一个。任何以后的异常都将丢失。即如果 t2 和 t3 抛出异常,则只有 t2 会被捕获;等。随后的任务异常将不被观察到。
Where as in the WhenAll - if any or all of the tasks fault, the resulting task will contain all of the exceptions. The await keyword still always re-throws the first exception. So the other exceptions are still effectively unobserved. One way to overcome this is to add an empty continuation after the task WhenAll and put the await there. This way if the task fails, the result property will throw the full Aggregate Exception:
在 WhenAll 中 - 如果任何或所有任务出错,则生成的任务将包含所有异常。await 关键字仍然总是重新抛出第一个异常。所以其他异常仍然有效地未被观察到。解决这个问题的一种方法是在任务 WhenAll 之后添加一个空的延续并将等待放在那里。这样,如果任务失败,结果属性将抛出完整的聚合异常:
static async Task DoWork2() //modified to catch all exceptions
{
try
{
var t1 = DoTask1Async("t1.1", 3000);
var t2 = DoTask2Async("t1.2", 2000);
var t3 = DoTask3Async("t1.3", 1000);
var t = Task.WhenAll(t1, t2, t3);
await t.ContinueWith(x => { });
Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t.Result[0], t.Result[1], t.Result[2]));
}
catch (Exception x)
{
// ...
}
}