64

如果我不关心任务完成的顺序,只需要它们全部完成,我还应该使用await Task.WhenAll而不是多个await? E.g,是DoWord2低于首选方法DoWork1(为什么?):为什么我应该更喜欢单个'等待Task.WhenAll'多个等待?

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

如果您实际并不知道需要并行执行多少任务,该怎么办?如果你有1000个任务需要运行,该怎么办?第一个不会有太多的可读性等待t1;等待t2; ....;等待tn' =>第二个总是在这两种情况下最好的选择 –

+0

您的评论是有道理的。我只是想为自己澄清一些事情,与我最近回答的另一个问题有关[http://stackoverflow.com/questions/18291299/how-to-wait-until-await-async-methods-finish/)。在那种情况下,有3个任务。 – avo

回答

60

是,使用WhenAll因为它同时传播的所有错误。如果多次等待,你会失去错误,如果其中一个较早的等待投掷。

另一个重要的区别是WhenAll将等待全部任务完成。 await的链将在第一次例外时中止等待,但仍未执行未等待的任务。这会导致意外的并发。

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

+4

“,因为它一次传播所有错误”如果你“等待”它的结果,请不要。 – svick

+1

@svick hm这是真实的(并且不合需要)。我现在正在调查这个问题:http://stackoverflow.com/questions/18314961/await-should-throw-aggregateexception-not-just-the-first-exception – usr

+0

对不起,迟到的评论,但我只是碰巧“通过“,我认为使用WhenAll赞成等待每个任务的主要原因与性能有关。在等待每个任务分别有效地“序列化”任务的执行时,WhenAll有可能在最长运行的单个任务的时间范围内完成所有任务,因为它们将并行执行(如果执行环境支持它) 。我认为应该对接受的答案进行编辑以反映这一点。 –

18

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

  • 开始与给定context
  • 保存背景
  • 等待T1
  • 恢复原来的背景下
  • 保存上下文
  • 等待T2
  • 恢复原来的背景下
  • 保存上下文
  • 等待T3
  • 恢复原来的背景下

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

  • 从给定的上下文开始
  • 保存上下文
  • 等待所有的T1,T2和T3
  • 的恢复原来的背景下

这是否是一个足够大的交易为您的特定情况下,当然,“上下文相关“(原谅双关语)。

+1

你似乎认为发送消息到同步上下文是昂贵的。它真的不是。你有一个被添加到队列中的委托,该队列将被读取并且委托被执行。这增加的开销是诚实地非常小。这不是什么,但也不是很大。无论异步操作的花费如何,几乎在所有情况下都会使这种开销变得渺茫。 – Servy

+0

同意,这只是我能想到的比较喜欢一个的唯一原因。那么,加上与Task.WaitAll的相似之处,其中线程切换是一个更重要的成本。 –

+1

@Servy马塞尔指出,真的取决于。例如,如果您在所有db任务上使用await,并且该db与asp.net实例位于同一台计算机上,则有些情况下您将等待数据库命中,这是内存中索引中的,更便宜比那个同步开关和线程池混洗。在这种情况下,可能会有一个与WhenAll()的重大整体胜利,所以......这真的取决于。 –

10

异步方法作为状态机实现。有可能编写方法以使它们不被编译到状态机中,这通常被称为快速异步方法。这些可以实现像这样:

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

对这个问题的其他答案提供技术上的原因,为什么await Task.WhenAll(t1, t2, t3);是首选。这个答案的目的是从一个较软的一面(这是@usr暗示的)来看它,但仍然会得出同样的结论。

await Task.WhenAll(t1, t2, t3);是一种更实用的方法,因为它声明了意图并且是原子的。

随着await t1; await t2; await t3;,没有任何东西阻止队友(或者甚至你的未来自己!)在个人await陈述之间添加代码。当然,你已经将它压缩到一行来实现这一目标,但这并不能解决问题。此外,在团队设置中,在给定的代码行中包含多个语句通常是不好的形式,因为它可以使人眼更难以扫描源文件。

简而言之,await Task.WhenAll(t1, t2, t3);更易于维护,因为它可以更清楚地传达您的意图,并且不易受到可能来自代码的良性更新或甚至合并出错的特殊错误的影响。