我运行以下代码:
var cancellation = new CancellationTokenSource();
var cancelledTask1 = .....;//starting new long-running task that accepts cancellation.Token
var cancelledTask2 = .....;//starting new long-running task that accepts cancellation.Token
//then I request cancellation
cancellation.Cancel();
//some task gets cancelled before code below executes
try
{
//wait for completion (some task is already in cancelled state)
await Task.WhenAll(cancelledTask1, cancelledTask2);
}
catch (OperationCanceledException e)
{
Logger.Debug("await WhenAll", e);
}
我得到
await WhenAll System.Threading.Tasks.TaskCanceledException: A task was canceled.
我认为它之所以失败是因为某些任务已经处于取消状态。为什么Task.WhenAll
方法会在取消子任务的情况下破坏正常流并抛出异常?这种行为的好处是什么?
然后,我尝试了Task.WhenAny
:方法
var cancellation = new CancellationTokenSource();
var cancelledTask3 = .....;//starting new long-running task that accepts cancellation.Token
//then I request cancellation
cancellation.Cancel();
//the task gets cancelled before code below executes
try
{
//wait for completion (the task is already in cancelled state)
await Task.WhenAny(cancelledTask3);
}
catch (OperationCanceledException e)
{
Logger.Debug("await WhenAny", e);
}
而且它不会抛出异常。
第二个问题是:为什么Task.WhenAny
在相同的情况下不抛出异常?我希望这两种方法都应该以相同的方式处理取消的任务:要么抛出异常,要么不抛出异常。
Task.WhenAny
被设计为在任何任务完成时完成,其中完成包括失败。事实上,当其中一项任务可能失败时,我发现它最有用。例如:
try
{
await Task.WhenAny(task1,task2);
cancellationToken.Cancel(); //cancel all tasks
await Task.WhenAll(task1,task2); //wait for both tasks to respect the cancellation
}
catch (Exception x)
{
...
}
在这里,我只需要完成其中一个任务(因此,WhenAny
)。在这种情况下,我也想取消其他任务。然后,我调用WhenAll
以等待其他任务尊重取消请求,并在发生异常时传播异常。
换句话说,Task.WhenAny
的设计目的是让您在其他任务仍在运行时执行某些操作,并且它不抛出异常以允许您对其他任务执行任何您想要的操作,这很有用。Task.WhenAll
仅在所有任务完成(成功或不成功)时完成。它可以抛出异常,因为没有什么可处理的了,计算已经完成。
Task.WhenAny
返回Task<Task>
,而Task.WhenAll
返回Task
。无论第一个已完成/已取消/出现故障的任务处于何种状态,外部任务都会成功完成。内部任务是第一个被取消的任务。因此,必须等待内部和外部任务,才能抛出OperationCanceledException
。
例如:
await await Task.WhenAny(cancelledTask3);