2015-01-21 35 views
2

任何人都可以請解釋這一點,也許我失去了明顯的東西。任務構造函數與Task.Run與異步操作 - 不同的行爲

這兩個案例在行爲上似乎是相同的,但它們並非如此。

案例1

var t = Task.Run(async() => { await Task.Delay(2000); }); 
  • 的第二個任務等待第一個:

    • 開始與asyncAction,,做一些工作了一段時間的任務

      var waitingTask = Task.Run(() => { t.Wait(); }); 
      
    • 等待第二個任務:

      waitingTask.Wait(); 
      

    案例2

    • 建立一個Task使用Task構造,傳球同樣asyncAction

      var t = new Task(async() => { await Task.Delay(2000); }); 
      
    • 啓動另一個任務等待第一個(就像在第一種情況下):

      var waitingTask = Task.Run(() => { t.Wait(); }); 
      
    • 開始第一個任務:

      t.Start(); 
      
    • 等待第二個任務:

      waitingTask.Wait(); 
      

    第一種情況的行爲如預期:等待任務在第一個完成後結束,2秒後結束。

    第二種情況很奇怪:等待任務很快結束,早在第一個例子之前很久。

    從兩個任務打印消息時很容易看到。第二項任務結束時的打印將顯示不同之處。

    我正在使用VS 2015預覽版,如果這很重要,它可能會使用Roslyn進行編譯。

  • +2

    的可能重複的[任務內等待異步/等待(http://stackoverflow.com/questions/24777253/waiting-for-async-await-inside-a-task) – 2015-01-21 00:55:24

    回答

    4

    答案就在這裏http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

    實際上的問題是,隨着new Task,並且它只能採取Action,而你正在試圖給一個Func<Task>。這是前兩行,但使用正確的重載進行了重寫。

    Task t = Task.Run(async() => { await Task.Delay(2000); }); 
    

    Task<Task> t = new Task<Task>(async() => { await Task.Delay(2000); }); 
    

    首先創建如預期的任務。第二個創建一個任務,誰的返回類型是一個等待2000毫秒的任務。第一個示例,因爲以下重載...

    public Task Run(Func<Task>); 
    public Task<T> Run(Func<T>); 
    public Task<T> Run(Func<Task<T>>); 
    etc... 
    

    這些超載被設計成自動調用Task<Task<T>>.Unwrap()你。

    結果是在第二個示例中,您實際上正在等待第一個任務的開始/排隊。

    您可以修復由

    var t = new Task<Task>(async() => { await Task.Delay(2000); }).Unwrap(); 
    
    +1

    這與從http://stackoverflow.com/questions/24777253/waiting-for-async-await-inside-a-task – 2015-01-21 01:31:29

    +1

    接受的答案相同這篇文章完美地回答你的問題,雖然它錯過了一個重要的細節。你需要有一個任務來解開。它應該是'var t = new Task (async Task.Delay(2000))。Unwrap();'如果沒有泛型參數,你基本上聲明你的lambda沒有返回任何東西,所以等待的Task最終被調用通過消防和忘記。你的問題是爲什麼兩者有差別。答案是因爲一個沒有被解開,而另一個沒有被解開。應該怎麼做甚至不是我提出的重複問題的一部分。 – 2015-01-21 01:40:40

    1

    它鼓勵使用Task構造與async-await第二個例子中,你應該只使用Task.Run

    var t = Task.Run(async() => await Task.Delay(2000)); 
    

    Task構造函數之前建立的[基於任務的異步模式(TAP)] [1]和async-await。它會返回一個剛剛開始內部任務的任務,並繼續前進,而不用等待它完成(即開火併忘記)。

    你可以解決通過指定你想要的結果Task<Task>和使用Unwrap創建一個代表包括內任務的整個操作的任務:

    var t = new Task(async() => await Task.Delay(2000)); 
    t.Unwrap().Wait(); 
    

    使用Task.Run是簡單,做一切對你來說,包括Unwrap,因此您可以獲得代表整個async操作的Task

    +0

    可悲的是'Task.run'沒有幫助,如果你正在旋轉多個需要調用異步操作的長時間運行的任務,並且你需要在某個地方記錄這些任務。 – 2017-07-30 00:55:34

    +0

    @DouglasGaskell它爲什麼沒有幫助? 'Task.Run'也返回一個你可以跟蹤的任務。 – i3arnon 2017-07-30 09:49:48

    0

    從Aron的答案不是100%正確的。如果您使用建議的修復方法,您將最終得到:

    啓動可能不會在承諾式任務上調用。

    在我的情況下,我解決了類似的問題,通過引用包裝和解包任務。您使用wrappedTask.Start()來啓動任務,但unwrappedTask.Wait()要等待它(或訪問與任務執行相關的任何內容,例如其TaskStatus)。

    Source