任务取消异常与ContinueWith方法

10
我一直在试图找出我为什么会在最近开始出现问题的一些异步代码中收到TaskCanceledException。我将我的问题简化成了一个小的代码片段,让我感到困惑:
static void Main(string[] args)
{
    RunTest();
}

private static void RunTest()
{
    Task.Delay(1000).ContinueWith(t => Console.WriteLine("{0}", t.Exception), TaskContinuationOptions.OnlyOnFaulted).Wait();
}

据我所知,这应该只是暂停一秒钟然后关闭。 ContinueWith不会被调用(这仅适用于我的实际用例)。但是,我却收到了一个TaskCanceledException,而我不知道它来自哪里!

如果你将任务继续选项更改为“none”,它就能正常工作。 - Philip Stuyck
9
由于父任务未出错,从 ContinueWith 返回的继续任务已被取消。在这种情况下,您需要将父任务和继续任务分开,并等待父任务完成。 - Lee
3个回答

3

我也遇到了这个错误:

System.Threading.Tasks.TaskCanceledException: 'A task was canceled.'

代码块如下:

private void CallMediator<TRequest>(TRequest request) where TRequest : IRequest<Unit>
{
    _ = Task.Run(async () =>
    {
        var mediator = _serviceScopeFactory.CreateScope().ServiceProvider.GetService<IMediator>()!;
        await mediator.Send(request).ContinueWith(LogException, TaskContinuationOptions.OnlyOnFaulted);
    });
}

private void LogException(Task task)
{
    if (task.Exception != null)
    {
        _logger.LogError(task.Exception, "{ErrorMessage}", task.Exception.Message);
    }
}

阅读 ContinueWith 方法的文档,它有以下备注:
返回的任务将不会被调度执行,直到当前任务完成。如果通过 continuationOptions 参数指定的继续条件未满足,则继续任务将被取消而不是被安排。
对于我来说,首先调用了第一个任务 (mediator.Send(request)),然后继续执行任务 ContinueWith(...),这是我等待的任务。但是,由于第一个任务没有出现异常,第二个任务被取消了。因此,在等待第二个任务时,它抛出了一个 TaskCanceledException 异常。
我所做的是将代码更改为:
private void CallMediator<TRequest>(TRequest request) where TRequest : IRequest<Unit>
{
    _ = Task.Run(async () =>
    {
        var mediator = _serviceScopeFactory.CreateScope().ServiceProvider.GetService<IMediator>()!;
        try
        {
            _ = await mediator.Send(request);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "{ErrorMessage}", ex.Message);
        }
    });
}

我已经用普通的 try-catch 块替换了 .ContinueWith(...),以防所需任务失败。我认为这简化了代码并使其更易读。

在问题中,有这行代码:

Task.Delay(1000).ContinueWith(t => Console.WriteLine("{0}", t.Exception), TaskContinuationOptions.OnlyOnFaulted).Wait();

我会将其改写为:

try
{
    Task.Delay(1000).Wait();
}
catch (Exception ex)
{
    Console.WriteLine("{0}", ex);
}

2

2

如其他人所述,此调用仅需要在故障状态下具有前置任务,否则将抛出TaskCanceledException异常。对于此具体情况,您可以将ContinueWith泛化以处理所有状态:

await Task.Delay(1000).ContinueWith(
    task => 
        {
            /* take into account that Canceled-task throw on next row the TaskCancelledException */

            if (!task.IsFaulted) {
                return;
            }

            Console.WriteLine("{0}", task.Exception);
            // do smth like 'throw task.Exception.InnerException'
        });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接