如何正确取消Task.WhenAll并抛出第一个异常?

Rud*_*dey 4 .net c# exception task-parallel-library async-await

我有多个任务接受取消令牌并相应地调用ThrowIfCancellationRequested.这些任务将同时运行Task.WhenAll.我希望在任何任务抛出异常时取消所有任务.我使用Select和实现了这个ContinueWith:

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?
}
Run Code Online (Sandbox Code Playgroud)

我不确定这是否是最好的方法,它似乎有一些问题.看起来异常将在内部捕获,WhenAll始终到达代码.我不希望在WhenAll发生异常时到达后面的代码,我宁愿抛出异常,所以我可以在调用堆栈的另一个级别上手动捕获它.实现这一目标的最佳方法是什么?如果可能的话,我希望调用堆栈保持不变.如果发生多个异常,最好只重新抛出第一个异常,否则AggregateException.


在一个相关的说明,我试图通过取消令牌ContinueWith像这样:task.ContinueWith(lambda, cts.Token).然而,当任何任务发生异常时,这最终会抛出一个TaskCanceledException而不是我感兴趣的异常.我想我应该将取消令牌传递给ContinueWith因为这会取消ContinueWith它自己,我认为这不是我想要的.

Ste*_*ary 11

你不应该使用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)
{
  ...
}
Run Code Online (Sandbox Code Playgroud)

  • 这似乎是一个很好的解决方案.知道为什么这会有一个downvote吗? (2认同)