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);更易於維護,因爲它可以更清楚地傳達您的意圖,並且不易受到可能來自代碼的良性更新或甚至合併出錯的特殊錯誤的影響。