Reputation: 128
I'm trying to get tasks in C# to work for a specific use case but I'm not understanding how task continuation options affect the flow of tasks.
What I'm trying to do is get a series of tasks chained together with ContinueWith. This will look something like this:
A -> B -> C -> D
However, I want to include the option to short-circuit this in the event an error, so it should look like this:
A -> B -> C -> D -> X
So I put "OnlyOnRanToCompletion" as the task continuation option for each of the ContinueWith functions. Then, to catch the cancellation and return an error, I put a final task at the end of the chain with the task continuation option set to "OnlyOnCanceled".
The problem is that when this last block is hit, the continuation option is not met and the task then gets set to cancelled even if the original series of tasks was never cancelled.
What I want to happen is have A through D run, and if one of them results in a cancellation, skip the rest and run X. If A through D complete, the task shouldn't cancel. The solution needs to support an arbitrary number of continuations and will be created using LINQ.Expressions, so using async/await is probably not going to fly unless it's done creatively.
Some sample code that exhibits this is:
var cts = new CancellationTokenSource();
var token = cts.Token;
var t = Task.FromResult(1)
.ContinueWith(
x => x.Result + 1,
token,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default)
.ContinueWith(
x => x.Result + 1,
token,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default)
.ContinueWith(
x => -1,
token,
TaskContinuationOptions.OnlyOnCanceled,
TaskScheduler.Default);
The expected behavior here would be to return 3, and the status not completed.
The actual result is that the task is cancelled.
How do I do this?
Also, I can't use async
because my goal is to piggyback off TPL inside of something compiled from LINQ.Expressions so that it can evaluate asynchronously and handle errors at the end without throwing any exceptions.
Upvotes: 0
Views: 305
Reputation: 128
Figured it out - to get the last continuation to run regardless of whether or not the previous continuations completed and without setting the status to canceled do this:
Change the continuation option of the last continuation to TaskContinuation.None
so that it always runs, so it won't cancel if it gets here with a status of completed.
Don't pass in a cancellation token to the last continuation because passing in a cancellation token that has been cancelled seems to have the effect of causing the continuation to cancel if it would have otherwise run without the token.
Upvotes: 1
Reputation: 203802
See the remarks for ContinueWith
for an explanation of this behavior:
The returned Task will not be scheduled for execution until the current task has completed. If the criteria specified through the continuationOptions parameter are not met, the continuation task will be canceled instead of scheduled.
Since the criteria for your last ContinueWith
call weren't met, the Task
returned from that call was cancelled.
Upvotes: 0