正确处理任务取消的一般方法

General approach to handle Task cancellation correctly

我正在进行代码审查,我很担心这种模式,在该代码中随处可见:

try
{
    await DoSomethingAsync();
    await DoSomethingElseAsync();
    // and so on...
}
catch (OperationCanceledException)
{
    // all good, user cancelled
    // log and return
    return;
}
// handle other particular exceptions
// ...
catch (Exception ex)
{
    // fatal error, alert the user
    FatalErrorMessage(ex);
}

我关心的部分是处理 OperationCanceledException。这段代码不应该也处理 AggregateException 并检查唯一的 inner 异常是否是 OperationCanceledException?

我知道 Task.WaitTask.Result 会像那样抛出 AggregateException,而不是 OperationCanceledException。代码的作者向我保证她只使用 async/await 而从不使用 Wait/Result。因此,她不喜欢额外观察 AggregateException 以进行取消的想法。然而,我的观点是一些基于标准 Task 的 BCL API 仍然可以用 AggregateException 包装 OperationCanceledException,例如因为他们仍然可能在内部访问 Task.Result

有意义吗?我们是否应该担心同时处理 OperationCanceledExceptionAggregateException 以正确观察取消?

好吧,这在技术上绝对可行,使用此代码很容易验证:

static void Main()
{
    Test().Wait();
}

static async Task Test()
{
    try
    {
        await ThrowAggregate();
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
    }
}

static async Task ThrowAggregate()
{
    ThrowException().Wait();
}

static async Task ThrowException()
{
    throw new OperationCanceledException();
}

ThrowAggregate 将 AggregateException 存储在返回的任务中,因此等待它仍然会抛出 AggregateException。所以如果你想勤奋,你也需要抓住 AggregateException

但是,非常不可能BCL 中的任何方法都可以做到这一点,如果它做到了,那么您遇到的问题比异常处理还大,因为您进行异步优于同步。 我会更担心你自己的代码。

However, my point is some standard Task-based BCL APIs could still be wrapping OperationCanceledException with AggregateException, e.g. because they still might be accessing Task.Result internally.

不,他们不会那样做。

Should we be worrying about handling both OperationCanceledException and AggregateException to observe cancellation correctly?

我会说不。当然,可能 一个 AggregateException 可以包含一个 OperationCanceledException,但它也可以很容易地包含 other particular exceptions

只要您遵循异步最佳实践(即没有异步同步或异步同步),那么您就不必担心这一点。