2017-09-15 70 views
2

我測試C#異步的asynchronousity /等待和跨越一個驚喜,其中用於ContinueWith隨後的代碼不等待前面的任務完成就來了:C#鏈式ContinueWith不是在等待前一個任務來完成

public async Task<int> SampleAsyncMethodAsync(int number,string id) 
    { 
      Console.WriteLine($"Started work for {id}.{number}"); 
      ConcurrentBag<int> abc = new ConcurrentBag<int>(); 

      await Task.Run(() => { for (int count = 0; count < 30; count++) { Console.WriteLine($"[{id}] Run: {number}"); abc.Add(count); } }); 

      Console.WriteLine($"Completed work for {id}.{number}"); 
      return abc.Sum(); 
     } 

 [Test] 
     public void TestAsyncWaitForPreviousTask() 
     { 
      for (int count = 0; count < 3; count++) 
      { 
       int scopeCount = count; 

       var c = SampleAsyncMethodAsync(0, scopeCount.ToString()) 
       .ContinueWith((prevTask) => 
       { 
        return SampleAsyncMethodAsync(1, scopeCount.ToString()); 
       }) 
       .ContinueWith((prevTask2) => 
       { 
        return SampleAsyncMethodAsync(2, scopeCount.ToString()); 
       }); 
      } 
     } 

輸出顯示運行執行0.0,1.0和2.0正確異步執行,但隨後的X.1和十,得到幾乎立即開始和十,實際完成:這是與下面的測試方法執行

在x.1之前。例如。如下記錄:

[2] Run: 0 
[2] Run: 0 
[2] Run: 0 
Completed work for 2.0 
Started work for 0.1 
Started work for 0.2 <-- surprise! 
[0] Run: 2 
[0] Run: 2 
[0] Run: 2 
[0] Run: 2 
[0] Run: 2 

看來continueWith僅將在第一個任務(0)等,無論後續的鏈條。 我可以通過在第一個Continuewith塊中嵌套第二個ContinueWith來解決問題。

我的代碼有問題嗎?我假設Console.WriteLine尊重FIFO。

+0

['ContinueWithWith'是一種極其低級的管道方法](http://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html)。你應該使用'await'而不是'ContinueWith'。 –

回答

3

總之,您期望ContinueWith等待先前返回的對象。返回一個對象(即使是TaskContinueWith動作對返回值沒有任何作用,它不會等待它完成,它會返回它並在存在時傳遞給繼續。

以後的事確實發生了:

  • 您運行SampleAsyncMethodAsync(0, scopeCount.ToString())
  • 當它完成後,您執行的延續1:

    return SampleAsyncMethodAsync(1, scopeCount.ToString()); 
    

    ,當它絆倒在await Task.Run,它返回一個任務。即,它不等待SampleAsyncMethodAsync完成。

  • 然後,繼續1被認爲是完成的,因爲它已經返回一個值(任務)
  • 繼續2被運行。

如果手動等待每異步方法,那麼它會因此運行:

for (int count = 0; count < 3; count++) 
{ 
    int scopeCount = count; 

    var c = SampleAsyncMethodAsync(0, scopeCount.ToString()) 
    .ContinueWith((prevTask) => 
    { 
     SampleAsyncMethodAsync(1, scopeCount.ToString()).Wait(); 
    }) 
    .ContinueWith((prevTask2) => 
    { 
     SampleAsyncMethodAsync(2, scopeCount.ToString()).Wait(); 
    }); 
}  

使用ContinueWith(async t => await SampleAsyncMethodAsync...不工作爲好,因爲它會導致成包裹Task<Task>結果(很好地解釋here)。

此外,你可以這樣做:

for (int count = 0; count < 3; count++) 
{ 
    int scopeCount = count; 

    var c = SampleAsyncMethodAsync(0, scopeCount.ToString()) 
     .ContinueWith((prevTask) => 
     { 
      SampleAsyncMethodAsync(1, scopeCount.ToString()) 
       .ContinueWith((prevTask2) => 
       { 
        SampleAsyncMethodAsync(2, scopeCount.ToString()); 
       }); 
     }); 
} 

但是,它會創建某種回調地獄和看起來雜亂無章。

您可以使用await使這個代碼乾淨了一點:

for (int count = 0; count < 3; count++) 
{ 
    int scopeCount = count; 

    var d = Task.Run(async() => { 
     await SampleAsyncMethodAsync(0, scopeCount.ToString()); 
     await SampleAsyncMethodAsync(1, scopeCount.ToString()); 
     await SampleAsyncMethodAsync(2, scopeCount.ToString()); 
    }); 
} 

現在,它運行3個計數3個任務,每個任務都會因此與運行異步方法number等於1,2, 3。

+0

是的,ContinueWith的行爲確實確認行爲,但是MSDN聲明該方法返回一個新的延續任務。 https://msdn.microsoft.com/en-us/library/dd270696(v=vs.110).aspx – TropicalFruitz

+0

@TropicalFruitz好的,現在我明白你的意思了,你說得對。 'ContinueWith'確實返回一個全新的任務。它看起來好像與使用'async'有關。使'SampleAsyncMethodAsync'不是異步會讓你的代碼運行正確。非常有趣,如果我理解或刪除它,我會更新我的答案。 –

+0

@TropicalFruitz請你看看最新的答案。我無法解釋它:( –