Eric Dand
Eric Dand

Reputation: 1210

How to catch an OperationCanceledException when using ContinueWith

I have some code I'm downgrading from .NET 4.5's lovely async and await keywords to .NET 4.0. I'm using ContinueWith to create a continuation similar to the way await works.

Basically, my old code was:

var tokenSource = newCancellationTokenSource();
var myTask = Task.Run(() =>
{
    return MyStaticClass.DoStuff(tokenSource.Token);
}, tokenSource.Token);
try
{
    var result = await myTask;
    DoStuffWith(result);
}
catch (OperationCanceledException)
{
    // Cancel gracefully.
}

(As one might expect, MyStaticClass.DoStuff(token) regularly calls token.ThrowIfCancellationRequested().)

My new code looks like this:

var tokenSource = new CancellationTokenSource();
try
{
    Task.Factory.StartNew(() =>
    {
        return MyStaticClass.DoStuff(tokenSource.Token);
    }, tokenSource.Token)
    .ContinueWith(task =>
    {
        var param = new object[1];
        param[0] = task.Result;
        // I need to use Invoke here because "DoStuffWith()" does UI stuff.
        Invoke(new MyDelegate(DoStuffWith, param));
    });
}
catch (OperationCanceledException)
{
    // Cancel gracefully.
}

However, the OperationCanceledException is never caught. What's going on? Where do I put my try/catch block?

Upvotes: 5

Views: 3278

Answers (1)

Lucas Trzesniewski
Lucas Trzesniewski

Reputation: 51420

Cancellation is handled differently from other exceptions. Basically, you can use this pattern:

Task.Factory.StartNew(() =>
{
    // The task
}, tokenSource.Token)
.ContinueWith(task =>
{
    // The normal stuff
}, TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith(task =>
{
    // Handle cancellation
}, TaskContinuationOptions.OnlyOnCanceled)
.ContinueWith(task =>
{
    // Handle other exceptions
}, TaskContinuationOptions.OnlyOnFaulted);

Or the alternative one:

Task.Factory.StartNew(() =>
{
    // The task
}, tokenSource.Token)
.ContinueWith(task =>
{
    switch (task.Status)
    {
    case TaskStatus.RanToCompletion:
        // The normal stuff
        break;
    case TaskStatus.Canceled:
        // Handle cancellation
        break;
    case TaskStatus.Faulted:
        // Handle other exceptions
        break;
    }
});

In your case, you're not catching anything because:

  • Task.Factory.StartNew returns immediately and always succeeds.
  • Your continuation always runs
  • Accessing task.Result throws an AggregateException since the task is canceled
  • The exception is not handled by anything since it's thrown from a thread pool thread. Oops. What happens next depends on the framework version:

    • In .NET < 4.5, the process will be terminated as soon as the failing task is finalized, since you have an unobserved exception.
    • In .NET >= 4.5, the exception will be silently dropped.

Upvotes: 10

Related Questions