Handle task cancellations elegantly

When using tasks for large / long running workloads that I need to cancel, I often use a pattern like this for the action the task performs:

public void DoWork(CancellationToken cancelToken)
{
    try
    {
        //do work
        cancelToken.ThrowIfCancellationRequested();
        //more work
    }
    catch (OperationCanceledException)
    {
        throw;
    }
    catch (Exception ex)
    {
        Log.Exception(ex);
        throw;
    }
}

      

An OperationCanceledException should not be logged as an error, but should not be swallowed if the task is to transition to a canceled state. Any other exceptions should not be considered under this method.

It always seemed a little awkward and visual studio breaks the throw for OperationCanceledException by default (although I have "break on User-unhandled" disabled now for OperationCanceledException due to my use of this pattern).

Ideally, I think I would like to do something like this:

public void DoWork(CancellationToken cancelToken)
{
    try
    {
        //do work
        cancelToken.ThrowIfCancellationRequested();
        //more work
    }
    catch (Exception ex) exclude (OperationCanceledException)
    {
        Log.Exception(ex);
        throw;
    }
}

      

i.e. have some sort of list of exceptions applied to catch, but without language support, which is currently not possible (@ eric-lippert: C # vNext :)).

Another way would be to continue:

public void StartWork()
{
    Task.Factory.StartNew(() => DoWork(cancellationSource.Token), cancellationSource.Token)
        .ContinueWith(t => Log.Exception(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
}

public void DoWork(CancellationToken cancelToken)
{
    //do work
    cancelToken.ThrowIfCancellationRequested();
    //more work
}

      

but I don't really like that as an exception can technically have more than one inner exception and you don't have as much context when registering an exception as in the first example (if I did more than just register it).

I realize this is a bit of a style issue, but I wonder if anyone has any better suggestions?

Do I just need to stick with example 1?

Eamon

+31


source to share


6 answers


So what's the problem? Just drop the block catch (OperationCanceledException)

and set the correct continuations:

var cts = new CancellationTokenSource();
var task = Task.Factory.StartNew(() =>
    {
        var i = 0;
        try
        {
            while (true)
            {
                Thread.Sleep(1000);

                cts.Token.ThrowIfCancellationRequested();

                i++;

                if (i > 5)
                    throw new InvalidOperationException();
            }
        }
        catch
        {
            Console.WriteLine("i = {0}", i);
            throw;
        }
    }, cts.Token);

task.ContinueWith(t => 
        Console.WriteLine("{0} with {1}: {2}", 
            t.Status, 
            t.Exception.InnerExceptions[0].GetType(), 
            t.Exception.InnerExceptions[0].Message
        ), 
        TaskContinuationOptions.OnlyOnFaulted);

task.ContinueWith(t => 
        Console.WriteLine(t.Status), 
        TaskContinuationOptions.OnlyOnCanceled);

Console.ReadLine();

cts.Cancel();

Console.ReadLine();

      



TPL distinguishes between cancellation and error. Hence, canceling (i.e. Throwing OperationCancelledException

inside the task body) is not an error.

The main thing is not to handle exceptions inside the task body without throwing them.

+15


source


This is how you elegantly handle task cancellation:

Working with fire and oblivion problems

var cts = new CancellationTokenSource( 5000 );  // auto-cancel in 5 sec.
Task.Run( () => {
    cts.Token.ThrowIfCancellationRequested();

    // do background work

    cts.Token.ThrowIfCancellationRequested();

    // more work

}, cts.Token ).ContinueWith( task => {
    if ( !task.IsCanceled && task.IsFaulted )   // suppress cancel exception
        Logger.Log( task.Exception );           // log others
} );

      



Waiting Handling Waiting for Task Completion / Cancellation

var cts = new CancellationTokenSource( 5000 ); // auto-cancel in 5 sec.
var taskToCancel = Task.Delay( 10000, cts.Token );  

// do work

try { await taskToCancel; }           // await cancellation
catch ( OperationCanceledException ) {}    // suppress cancel exception, re-throw others

      

+9


source


C # 6.0 has a solution for this .. Filtering Exceptions

int denom;

try
{
     denom = 0;
    int x = 5 / denom;
}

// Catch /0 on all days but Saturday

catch (DivideByZeroException xx) when (DateTime.Now.DayOfWeek != DayOfWeek.Saturday)
{
     Console.WriteLine(xx);
}

      

+6


source


According to this MSDN blog post , you should catch OperationCanceledException

for example

async Task UserSubmitClickAsync(CancellationToken cancellationToken)
{
   try
   {
      await SendResultAsync(cancellationToken);
   }
   catch (OperationCanceledException) // includes TaskCanceledException
   {
      MessageBox.Show('Your submission was canceled.');
   }
}

      

If your method to be canceled is in between other canceled operations, you may need to clean up when canceled. That being said, you can use the above catch block, but don't forget to rethrow it correctly:

async Task SendResultAsync(CancellationToken cancellationToken)
{
   try
   {
      await httpClient.SendAsync(form, cancellationToken);
   }
   catch (OperationCanceledException)
   {
      // perform your cleanup
      form.Dispose();

      // rethrow exception so caller knows youve canceled.
      // DONT 'throw ex;' because that stomps on 
      // the Exception.StackTrace property.
      throw; 
   }
}

      

+1


source


You could do something like this:

public void DoWork(CancellationToken cancelToken)
{
    try
    {
        //do work
        cancelToken.ThrowIfCancellationRequested();
        //more work
    }
    catch (OperationCanceledException) when (cancelToken.IsCancellationRequested)
    {
        throw;
    }
    catch (Exception ex)
    {
        Log.Exception(ex);
        throw;
    }
}

      

0


source


I'm not really sure what you are trying to achieve here, but I think the following pattern might help

public void DoWork(CancellationToken cancelToken)
{
    try
    {
        //do work
        cancelToken.ThrowIfCancellationRequested();
        //more work
    }
    catch (OperationCanceledException) {}
    catch (Exception ex)
    {
        Log.Exception(ex);
    }
}

      

You may have noticed that I removed the throw statement from here. This will not throw an exception, it will simply ignore it.

Let me know if you intend to do anything else.

There is another way that is very close to what you showed in your code

    catch (Exception ex)
    {
        if (!ex.GetType().Equals(<Type of Exception you don't want to raise>)
        {
            Log.Exception(ex);

        }
    }

      

-3


source







All Articles