10

I've been trying to figure out why I'm getting a TaskCanceledException for a bit of async code that has recently started misbehaving. I've reduced my issue down to a small code snippet that has me scratching my head:

static void Main(string[] args)
{
    RunTest();
}

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

As far as I'm aware, this should simply pause for a second and then close. The ContinueWith won't be called (this only applies to my actual use-case). However, instead I'm getting a TaskCanceledException and I've no idea where that is coming from!

Barguast
  • 5,926
  • 9
  • 43
  • 73
  • It is related to your taskcontinuationoptions if you change it to none it works. – Philip Stuyck Feb 20 '15 at 16:44
  • 9
    The continuation task returned from `ContinueWith` is cancelled because the parent task didn't fault. You need to separate the parent and continuation tasks in this case and wait for the parent. – Lee Feb 20 '15 at 16:45

3 Answers3

3

I also received this error:

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

The block of code looked like this:

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);
    }
}

Reading the documentation for the ContinueWith method, it has the following remarks:

The returned Task will not be scheduled for execution until the current task has completed. If the continuation criteria specified through the continuationOptions parameter are not met, the continuation task will be canceled instead of scheduled.

So for me, it called the first task (mediator.Send(request)), then it continued with the task ContinueWith(...), which is the one I awaited. However, since an exception had not occurred in the first task, the second task was cancelled. Therefore, when awaiting the second task, it threw a TaskCanceledException.

What I did, was to change the code to this:

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);
        }
    });
}

Instead of using .ContinueWith(...), I have replaced it with just a regular try-catch block in case of the task I am interested in fails. I think this simplifies the code and makes it more readable.

In the question, there is this line of code:

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

I would rewrite it to:

try
{
    Task.Delay(1000).Wait();
}
catch (Exception ex)
{
    Console.WriteLine("{0}", ex);
}
Daniel Jonsson
  • 3,261
  • 5
  • 45
  • 66
2

You are using the wrong taskcontinuationoption:

See following link : https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions%28v=vs.110%29.aspx

It says : Specifies that the continuation task should be scheduled only if its antecedent threw an unhandled exception. This option is not valid for multi-task continuations.

Philip Stuyck
  • 7,344
  • 3
  • 28
  • 39
2

As guys said above this call requires just antecedent-task in faulted-status otherwise will throw TaskCanceledException, for this concrete case you can generalize ContinueWith to process all statuses:

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'
        });
vladimir
  • 13,428
  • 2
  • 44
  • 70