System.Threading.Tasks.TaskCanceledException

Represents an exception used to communicate task cancellation.

Minimum version: >= 4.0 >= Core 1.0

Statistics

23
elmah.io logo 4

How to handle it

try
{

}
catch (System.Threading.Tasks.TaskCanceledException e)
{

}
try
{

}
catch (System.Threading.Tasks.TaskCanceledException e) when (e.Message.Contains("something"))
{

}
try
{

}
catch (System.Threading.Tasks.TaskCanceledException e) when (LogException(e))
{

}

private static bool LogException(Exception e)
{
    logger.LogError(...);
    return false;
}

How to avoid it

We haven't written anything about avoiding this exception yet. Got a good tip on how to avoid throwing System.Threading.Tasks.TaskCanceledException? Feel free to reach out through the support widget in the lower right corner with your suggestions.

Links

YouTube videos

Possible fixes from StackOverflow

If you add ContinueWith() with an empty action, the exception isn't thrown. The exception is caught and passed to the tsk.Exception property in the ContinueWith(). But It saves you from writing a try/catch that uglifies your code.

await Task.Delay(500, cancellationToken.Token).ContinueWith(tsk => { });

UPDATE:

Instead of writing code to handle the exception, a boolean would be much cleaner. This is only preferred when a delay cancel is expected!. One way is to create a helper class (Although I don't like helper classes much)

namespace System.Threading.Tasks
{
    public static class TaskDelay
    {
        public static Task<bool> Wait(TimeSpan timeout, CancellationToken token) =>
            Task.Delay(timeout, token).ContinueWith(tsk => tsk.Exception == default);

        public static Task<bool> Wait(int timeoutMs, CancellationToken token) =>
            Task.Delay(timeoutMs, token).ContinueWith(tsk => tsk.Exception == default);
    }
}

For example:

var source = new CancellationTokenSource();

if(!await TaskDelay.Wait(2000, source.Token))
{
    // The Delay task was canceled.
}

(don't forget to dispose the source)

The difference here comes from using token.ThrowIfCancellationRequested(). This method checks for cancellation and if requested throws OperationCanceledException specifically and not TaskCanceledException (understandable as CancellationToken isn't exclusive to the TPL). You can look at the reference source and see that it calls this method:

private void ThrowOperationCanceledException()
{
    throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this);
}

"Regular" cancellation though will indeed generate a TaskCanceledException. You can see that by cancelling the token before the task had a chance to start running:

cancellationTokenSource.Cancel();
var task = Task.Run(() => { }, cancellationTokenSource.Token);
try
{
    await task; 
}
catch (Exception ex)
{
    Console.WriteLine(ex.ToString());
    Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}");
    Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}");
    Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}");
}

Output:

System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Sandbox.Program.<MainAsync>d__1.MoveNext()
Task.IsCanceled: True
Task.IsFaulted: False
Task.Exception: null

Traditional .Net methods usually don't use CancellationToken.ThrowIfCancellationRequested for async API as this is only appropriate when offloading work to another thread. These methods are for inherently asynchronous operations so cancellation is monitored using CancellationToken.Register (or the internal InternalRegisterWithoutEC).

Your problem is that SmtpClient is disposed before the email is sent.

Two ways:

  • Await the SendMailAsync result

    using (var client = new SmtpClient())
    {
        await client.SendMailAsync(email);
    }
    
  • Register the SendCompleted event and dispose the SmtpClient only after the message is sent

    var client = new SmtpClient();
    client.SendCompleted += (s, e) => {
        client.Dispose();
    };
    return client.SendMailAsync(message);
    

That's to be expected. When you cancel the old Delay, it will raise an exception; that's how cancellation works. You can put a simple try/catch around the Delay to catch the expected exception.

Note that if you want to do time-based logic like this, Rx is a more natural fit than async.

TaskCanceledException inherits from OperationCanceledException. So it least there is a little consitency.

if( ex is OperationCanceledException)
{
...
}