2017-02-21 104 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? –