Search code examples
c#taskcancellation

NotOnRanToCompletion Continuation doesn't run when parent task is cancelled


I'm trying to test a scenario where I have a task that can be cancelled, and a continuation that should be running if the antecedent task doesn't complete. A sample of the code is like this:

static void Main(string[] args)
{
    var source = new CancellationTokenSource();
    var task = Task.Factory.StartNew(() =>
        {
            while (true)
            {
                source.Token.ThrowIfCancellationRequested();
            }
        }, source.Token);

    var continuation = task.ContinueWith(t =>
        {
            Console.WriteLine("Continuation");
            if (t.Status == TaskStatus.Faulted)
            {
                Console.WriteLine("Antecedent Faulted: " + t.Exception.Message);
            }
        }, source.Token, TaskContinuationOptions.NotOnRanToCompletion | TaskContinuationOptions.AttachedToParent, TaskScheduler.Current);

    var cancellation = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(1000);
            source.Cancel();
        });

    try
    {
        continuation.Wait();
    }
    catch (AggregateException)
    {
        Console.WriteLine("AggregateException");
    }

    Console.WriteLine("Done");
    while (!Console.KeyAvailable) { }
}

The output of this program is:

AggregateException
Done

To a certain extent, I get it. The primary task was cancelled, which ends up throwing a TaskCanceledException which gets wrapped in the AggregateException. The question is, is this expected behaviour? If so, what use is TaskStatus.Faulted in a continuation, if that continuation isn't getting executed? I even set a looping check for ConsoleKeyAvailable just in case the continuation does get run as well as the AggregateException getting thrown.


Solution

  • I believe the reason this is occurring is you are passing source.Token as the CancellationToken for the call to task.ContinueWith. Despite passing NotOnRanToCompletion as the continuation options, the fact that the token is cancelled before the continuation task ever starts means the scheduler can immediately transition it to the canceled state without actually running it.