Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 635
Eric Dand Avatar asked May 14 '15 22:05

Eric Dand


People also ask

How do you handle a cancellation token?

The wait handle of the cancellation token will become signaled in response to a cancellation request, and the method can use the return value of the WaitAny method to determine whether it was the cancellation token that signaled. The operation can then just exit, or throw an OperationCanceledException, as appropriate.

Should I throw OperationCanceledException?

Don't throw OperationCanceledException after you've completed the work, just because the token was signaled. Return a successful result and let the caller decide what to do next.

Which object do you inspect to determine if a long running task will be Cancelled?

The background long-running tasks are cancelled by using the CancellationToken object in Blazor.


1 Answers

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.
like image 81
Lucas Trzesniewski Avatar answered Nov 14 '22 23:11

Lucas Trzesniewski