Search code examples
c#task-parallel-librarycancellation

Canceled task also appears as completed


I'm playing around with async-await and cancellation to get some more understanding on the matter. For this I have made the following console application:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncTest
{
    class Program
    {
        private static CancellationTokenSource _cancellationTokenSource;
        private static CancellationToken _cancellationToken;

        static void Main(string[] args)
        {
            Console.CancelKeyPress += myHandler;

            _cancellationTokenSource = new CancellationTokenSource();
            _cancellationToken = _cancellationTokenSource.Token;

            var task = DoWorkAsync(_cancellationToken).ContinueWith(ContinueMethod);
            task.Wait();

            Console.ReadLine();
        }

        protected static void myHandler(object sender, ConsoleCancelEventArgs args)
        {
            if (_cancellationToken.CanBeCanceled)
            {
                _cancellationTokenSource.Cancel();
            }
            args.Cancel = true;
        }

        static void ContinueMethod(Task task)
        {
            if (task.IsCanceled)
            {
                Console.WriteLine("The task was canceled");
            }

            if (task.IsCompleted)
            {
                Console.WriteLine("The task completed successfully");
            }

            if (task.IsFaulted)
            {
                if (task.Exception != null)
                {
                    var exceptions = task.Exception.Flatten().InnerExceptions;
                    foreach (var exception in exceptions)
                    {
                        Console.WriteLine(exception.Message);
                    }
                }
                Console.WriteLine("The task failed");
            }
        }

        static async Task DoWorkAsync(CancellationToken cancellationToken)
        {
            await Task.Run(() => DoWork(cancellationToken), cancellationToken);
        }

        static void DoWork(CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            Console.WriteLine("DoWork() is started");

            // Uncomment the following line of code to put the task in a 'faulted' state
            //throw new Exception();

            for (var count = 0; count < 10; count++)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    Console.WriteLine("Get a cancelation request");
                    cancellationToken.ThrowIfCancellationRequested();
                }
                else
                {
                    Thread.Sleep(500);
                    Console.WriteLine("Count : " + count);
                }
            }
            Console.WriteLine("DoWork() is finished");
        }
    }
}

When I let the application complete, I correctly receive the "The task completed successfully" message.

Now when I press CTRL+C, which triggers a cancel on the started task (see interception through myHandler), I correctly get the "The task was canceled" message. But I also get the "The task completed successfully" message. I was not expecting the task to also show up as complete, since I canceled it. In case I uncomment the throw new Exception(); line in the DoWork() method, I correctly receive the "The task failed" message, but also the "The task completed successfully" message. Am I wrong in my assumption and is this as designed? Or am I missing something else entirely?

I could off course work around this by adding an additional check as follows:

if (task.IsCompleted && !task.IsCanceled)
{
    Console.WriteLine("The task completed successfully");
}

But I'm not sure if this is the correct way or if something else in my program is causing this completed state.

Thanks in advance for your input and/or clarification on this matter.


Solution

  • The documentation of Task.IsCompleted says

    IsCompleted will return true when the task is in one of the three final states: RanToCompletion, Faulted, or Canceled.

    So IsCompleted tells you at least that the Task is not running any more. It does not indicate if the Task completed successfully, failed or was cancelled.