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.
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
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;
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With