Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.ContinueWith() executing but Task Status is still "Running"

Consider the following code:

MyTask = LongRunningMethod(progressReporter, CancelSource.Token)
    .ContinueWith(e => 
        { Log.Info("OnlyOnCanceled"); }, 
        default, 
        TaskContinuationOptions.OnlyOnCanceled,
        TaskScheduler.FromCurrentSynchronizationContext())
    .ContinueWith(e => 
        { Log.Info("OnlyOnFaulted"); }, 
        default, 
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.FromCurrentSynchronizationContext())
    .ContinueWith(e => 
        { Log.Info("OnlyOnRanToCompletion"); }, 
        default,
        TaskContinuationOptions.OnlyOnRanToCompletion, 
        TaskScheduler.FromCurrentSynchronizationContext())
    .ContinueWith(e => 
        { Log.Info("None"); }, 
        default, 
        TaskContinuationOptions.None,
        TaskScheduler.FromCurrentSynchronizationContext());
  • When I cancel the task using the provided CancelSource, output is:
    OnlyOnCanceled
    None
    as expected.

  • When LongRunningMethod throws an Exception output is:
    OnlyOnFaulted
    None
    as expected.

  • When LongRunningMethod completes output is:
    None
    so the ContinueWith with TaskContinuationOptions.OnlyOnRanToCompletion is not executed as I would expect.

I checked MyTask.Status in the last ContinueWith branch and it is still Running. So with that in mind, I would expect OnlyOnRanToCompletion to be skipped. The question is, why is the Status still Running? Looking at the debug output, I can see that LongRunningMethod ran to the end.

like image 765
Rev Avatar asked Jun 06 '20 15:06

Rev


2 Answers

It looks like you're chaining continuation tasks off each other rather than all off the original task. This will mean your TaskContinuationOptions are referring to the completion status of the previous task in the chain rather than the original parent (MyTask).
I would try something like the following (I can't try this exact version locally because I don't have all of your functions, but something similar worked for me).

    MyTask = LongRunningMethod(mods, Settings, progressReporter, CancelSource.Token);

    MyTask.ContinueWith(e =>
    {
        Log.Info("OnlyOnCanceled");
    }, default ,TaskContinuationOptions.OnlyOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());

    MyTask.ContinueWith(e =>
    {
        Log.Info("OnlyOnFaulted");
    }, default ,TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());

    MyTask.ContinueWith(e =>
    {
        Log.Info("OnlyOnRanToCompletion");
    }, default ,TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());

    MyTask.ContinueWith(e =>
    {
        Log.Info("None");
    }, default ,TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

This gave me:

OnlyOnRanToCompletion  
None
like image 124
Ross Gurbutt Avatar answered Nov 01 '22 20:11

Ross Gurbutt


As written in the docs:

you can specify that the continuation is to run only if the antecedent completes successfully, or only if it completes in a faulted state. If the condition is not true when the antecedent is ready to invoke the continuation, the continuation transitions directly to the TaskStatus.Canceled state and subsequently cannot be started.

That means that chaining ContinueWith calls will not work in your case because if first continuation will not match actual task status it will return canceled task to next chained call.

You can check that changing Task outcome and reordering continuation in this snippet:

var task = 
    //Task.FromCanceled(new CancellationToken(true))
    Task.FromException(new Exception())
    //Task.CompletedTask
        .ContinueWith(e => Console.WriteLine("OnlyOnCanceled"), TaskContinuationOptions.OnlyOnCanceled)
        .ContinueWith(e => Console.WriteLine("OnlyOnFaulted"), TaskContinuationOptions.OnlyOnFaulted)
        .ContinueWith(e => Console.WriteLine("OnlyOnRanToCompletion"), TaskContinuationOptions.OnlyOnRanToCompletion); 
Task.WaitAny(task); // to wait for task without exception and async
Console.WriteLine(task.Status);

Also setting up multiple separate continuations may be not an optimal solution, because you will be spawning multiple tasks when actually you need only one.

"Passing Data to a Continuation" section of the same doc suggests to analyze Task.Status property of the antecedent, for example:

Task.FromResult(1)
    .ContinueWith(t => 
    {   
        switch (t.Status)
        {
            case TaskStatus.RanToCompletion: Console.WriteLine("OnlyOnRanToCompletion"); return t.Result;
            case TaskStatus.Canceled: Console.WriteLine("OnlyOnCanceled"); return default;
            case TaskStatus.Faulted: Console.WriteLine("OnlyOnFaulted"); return default;
            default: return default;
        }
    });
like image 5
Guru Stron Avatar answered Nov 01 '22 21:11

Guru Stron