2017-02-21 97 views
1

我有多個任務接受取消令牌並相應地調用ThrowIfCancellationRequested。這些任務將使用Task.WhenAll同時運行。當任何任務拋出異常時,我希望取消所有任務。我實現了這個使用SelectContinueWith如何正確取消Task.WhenAll並拋出第一個異常?

var cts = new CancellationTokenSource(); 

try 
{ 
    var tasks = new Task[] { DoSomethingAsync(cts.Token), ... } // multiple tasks here 
     .Select(task => task.ContinueWith(task => 
     { 
      if (task.IsFaulted) 
      { 
       cts.Cancel(); 
      } 
     })); 

    await Task.WhenAll(tasks).ConfigureAwait(false); 
} 
catch (SpecificException) 
{ 
    // Why is this block never reached? 
} 

我不知道這是否是做到這一點的最佳方式,它似乎有一些問題。看起來異常會被內部捕獲,總是在WhenAll之後的代碼。我不想在發生異常時到達WhenAll之後的代碼,我寧願拋出異常,以便在調用堆棧的另一層手動捕獲它。達到此目的的最佳方式是什麼?如果可能的話,我希望調用堆棧保持不變。如果發生多個異常,最好是隻有第一個異常被重新拋出,否則不是AggregateException


在一個相關的說明,我試圖通過取消令牌ContinueWith像這樣:task.ContinueWith(lambda, cts.Token)。然而,當發生任何異常時,這最終會拋出一個TaskCanceledException而不是我感興趣的異常。我想我應該將取消標記傳遞給ContinueWith,因爲這會取消ContinueWith本身,我不認爲這是我想要的是。

+0

@Servy這個問題與「複製」之間的區別,這是關於'Task.WhenAll '並在任務外使用try-catch。另一個問題是關於連接多個'ContinueWith'並顯式檢查task.Exception。 –

+1

區別是不相關的。 'WhenAll'只是使用'ContinueWIth'附加它自己的延續,並且在確定是否應該自己排除故障時會檢查'Exception'的值,從而給你帶來完全相同的問題。 「WhenAll」幕後發生的一些情況與解釋問題或糾正問題沒有多大區別。 – Servy

+0

@Servy我不明白你的解釋或其他線程如何回答我的問題,如何讓異常像通常那樣被拋出,從而允許我在另一層調用堆棧上處理TPL之外的異常。 –

回答

5

您不應該使用ContinueWith。正確的答案是引入另一個「上級」 async方法,而不是延續連接到每一個任務:

private async Task DoSomethingWithCancel(CancellationTokenSource cts) 
{ 
    try 
    { 
    await DoSomethingAsync(cts.Token).ConfigureAwait(false); 
    } 
    catch 
    { 
    cts.Cancel(); 
    throw; 
    } 
} 


var cts = new CancellationTokenSource(); 
try 
{ 
    var tasks = new Task[] { DoSomethingWithCancel(cts), ... }; 
    await Task.WhenAll(tasks).ConfigureAwait(false); 
} 
catch (SpecificException) 
{ 
    ... 
} 
+2

這似乎是一個很好的解決方案。任何想法爲什麼這得到downvote? –