为什么我应该更喜欢单个“等待Task.WhenAll”多个等待?

如果我不关心任务完成的顺序,只需要它们全部完成,我还应该使用await Task.WhenAll而不是多个await ? 例如DoWord2DoWord2的首选方法(为什么?):

 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(); } } } 

是的,使用WhenAll因为它一次传播所有的错误。 有了多个等待,你会失去如果之前的等待投掷错误。

另一个重要的区别是,WhenAll将等待所有任务完成。 在第一个例外情况下,一连串的await将中止等待 ,但是不等待执行的任务仍在继续。 这会导致意外的并发。

我认为这也使阅读代码更容易,因为你想要的语义直接logging在代码中。

我的理解是,更喜欢Task.WhenAll多个await s的主要原因是性能/任务“搅动”: DoWork1方法做这样的事情:

  • 从给定的上下文开始
  • 保存上下文
  • 等待t1
  • 恢复原来的上下文
  • 保存上下文
  • 等待t2
  • 恢复原来的上下文
  • 保存上下文
  • 等待t3
  • 恢复原来的上下文

相比之下, DoWork2做到这一点:

  • 从给定的上下文开始
  • 保存上下文
  • 等待所有的t1,t2和t3
  • 恢复原来的上下文

当然,对于你的具体情况来说,这是一个足够大的交易,是“上下文相关的”(赦免双关语)。

一个asynchronous方法被实现为一个状态机。 编写方法是不可能被编译进状态机的,这通常被称为快速asynchronous方法。 这些可以这样实现:

 public Task DoSomethingAsync() { return DoSomethingElseAsync(); } 

当使用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); }