Cancelling a Task is throwing an exception

Razor picture Razor · Sep 8, 2011 · Viewed 73k times · Source

From what I've read about Tasks, the following code should cancel the currently executing task without throwing an exception. I was under the impression that the whole point of task cancellation was to politely "ask" the task to stop without aborting threads.

The output from the following program is:

Dumping exception

[OperationCanceledException]

Cancelling and returning last calculated prime.

I am trying to avoid any exceptions when cancelling. How can I accomplish this?

void Main()
{
    var cancellationToken = new CancellationTokenSource();

    var task = new Task<int>(() => {
        return CalculatePrime(cancellationToken.Token, 10000);
    }, cancellationToken.Token);

    try
    {
        task.Start();
        Thread.Sleep(100);
        cancellationToken.Cancel();
        task.Wait(cancellationToken.Token);         
    }
    catch (Exception e)
    {
        Console.WriteLine("Dumping exception");
        e.Dump();
    }
}

int CalculatePrime(CancellationToken cancelToken, object digits)
{  
    int factor; 
    int lastPrime = 0;

    int c = (int)digits;

    for (int num = 2; num < c; num++)
    { 
        bool isprime = true;
        factor = 0; 

        if (cancelToken.IsCancellationRequested)
        {
            Console.WriteLine ("Cancelling and returning last calculated prime.");
            //cancelToken.ThrowIfCancellationRequested();
            return lastPrime;
        }

        // see if num is evenly divisible 
        for (int i = 2; i <= num/2; i++)
        { 
            if ((num % i) == 0)
            {             
                // num is evenly divisible -- not prime 
                isprime = false; 
                factor = i; 
            }
        } 

        if (isprime)
        {
            lastPrime = num;
        }
    }

    return lastPrime;
}

Answer

Jon Skeet picture Jon Skeet · Sep 8, 2011

I am trying to avoid any exceptions when cancelling.

You shouldn't do that.

Throwing OperationCanceledException is the idiomatic way that "the method you called was cancelled" is expressed in TPL. Don't fight against that - just expect it.

It's a good thing, because it means that when you've got multiple operations using the same cancellation token, you don't need to pepper your code at every level with checks to see whether or not the method you've just called has actually completed normally or whether it's returned due to cancellation. You could use CancellationToken.IsCancellationRequested everywhere, but it'll make your code a lot less elegant in the long run.

Note that there are two pieces of code in your example which are throwing an exception - one within the task itself:

cancelToken.ThrowIfCancellationRequested()

and one where you wait for the task to complete:

task.Wait(cancellationToken.Token);

I don't think you really want to be passing the cancellation token into the task.Wait call, to be honest... that allows other code to cancel your waiting. Given that you know you've just cancelled that token, it's pointless - it's bound to throw an exception, whether the task has actually noticed the cancellation yet or not. Options:

  • Use a different cancellation token (so that other code can cancel your wait independently)
  • Use a time-out
  • Just wait for as long as it takes