Search code examples
c#.nettaskcancellation

Why task's status is Faulted instead of Canceled in cooperative cancellation?


My question is based on the answer to this question.

Let's say we have the following code:

class Program 
{
    static void Main(string[] args) 
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        Task<Int32> t = Task.Run(() => Sum(cts.Token, 100000));     
        
        // make sure the the task run first before 
        Thread.Sleep(500);   
        cancellation request occurs
        cts.Cancel();
      
        var status = t.Status;  // status is "Faulted"
        Console.ReadLine();
    }

    static Int32 Sum(CancellationToken ct, Int32 n) 
    {
        Int32 sum = 0;
        for (; n > 0; n--) 
        {
            // throws
            ct.ThrowIfCancellationRequested();    

            OperationCanceledException if cancellation request occurs
            checked { sum += n; } 
        }
        return sum;
    }
}

We can see that t's status is Faulted which is good.

According to the answer to the question, when we pass the token into task's constructor or Run's parameter, t's status should be "Canceled" because

when the task sees that OperationCanceledException, it checks whether the OperationCanceledException's token matches the Task's token. If it does, that exception is viewed as an acknowledgement of cooperative cancellation and the Task transitions to the Canceled state (rather than the Faulted state)

but even if we pass the token as

class Program 
{
    static void Main(string[] args) 
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        
        // pass the token into Run() method as the second parameter
        Task<Int32> t = Task.Run(() => Sum(cts.Token, 100000), cts.Token);     
        Thread.Sleep(500);
        cts.Cancel();

        // status is still "Faulted", not "Canceled"
        var status = t.Status;  
        Console.ReadLine();
    }
    //...
}

t's status is still Faulted not Canceled, so did I misunderstand the answer to the question or did I do something wrong in the code?


Solution

  • The task status is Faulted because the method throws an OverflowException before your code gets a chance to cancel it. If you check the Task.Exception.InnerException property, you'll find it's an OverflowException with this message:

    Arithmetic operation resulted in an overflow.

    That's because 500 ms is a very long time when it comes to running a loop that's just doing a tiny bit of math. And your loop uses checked arithmetic to perform the sum, and the sum of the numbers between 1 and 100,000 is just a bit over 5 billion, much larger than what an int can actually hold.

    The method easily reaches the overflow before the 500 ms it takes for your main thread to cancel the token.

    Similarly, if you remove the checked, the task will run to completion before you can cancel it.

    If you want to see the cancellation, add a call to Thread.Sleep(750) at the beginning of the Sum() method, so that it won't check the cancel token until the main thread has had a chance to cancel it. If you do that, then you will find the task in the Canceled state as you expected it to be.