Task.ContinueWith() executing but Task Status is still "Running"
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
It would be probably simpler to do the logging using async-await. This way you could avoid the old-school ContinueWith
method, and its confusing parameters and behavior.
public static async void OnCompletionLog(Task task, string name)
{
try
{
await task;
Log.Info($"{name} RanToCompletion");
}
catch (OperationCanceledException)
{
Log.Info($"{name} Canceled");
}
catch (Exception ex)
{
Log.Error(ex, $"{name} Failed");
}
}
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;
}
});