Consider the following C# code:
CancellationTokenSource tCancelSource = new CancellationTokenSource();
var tTask = Task.Factory.StartNew
(
() =>
{
// Start child
Task.Factory.StartNew
(() =>
{
Thread.Sleep(2000);
Console.WriteLine("A");
tCancelSource.Token.ThrowIfCancellationRequested();
}
, tCancelSource.Token
, TaskCreationOptions.AttachedToParent
, TaskScheduler.Current
)
.ContinueWith
(
t =>
{
Console.WriteLine("B");
}
, tCancelSource.Token, TaskContinuationOptions.AttachedToParent
, TaskScheduler.Current
);
}
, tCancelSource.Token, TaskCreationOptions.PreferFairness
, TaskScheduler.Current
)
.ContinueWith
(t => Console.WriteLine("C"));
Thread.Sleep(500);
tCancelSource.Cancel();
tTask.Wait();
Console.WriteLine("Done"); Console.Read();
The continuation of the child task does not fire, i.e. B is not printed to the Console. Commenting out the call to Cancel would print B.
Observed:
A
C
Expected
A
B
C
It seems that the continuation of a child task is not fired if the parent is cancelled, but I can’t find this documented. Anyone have any idea why/what’s going on here, or where this would be documented?
Edit:
I forgot to pass the cancellation token to the final continuation. Indeed, I found the documentation reads
“[…]If the antecedent was canceled, the continuation will not be started”, however I got confused by the option TaskContinuation.OnlyOnCanceled. Such a scenario is possible, for example
CancellationTokenSource tSource = new CancellationTokenSource();
Task tTest = Task.Factory.StartNew
(
() =>
{
throw new Exception("foobar");
}, tSource.Token).ContinueWith(t =>
{
Console.WriteLine("A " + t.Status);
}
, tSource.Token
, TaskContinuationOptions.NotOnFaulted
, TaskScheduler.Current
)
.ContinueWith
(
t =>
{
Console.WriteLine("B " + t.Status);
}
, tSource.Token
);
tTest.Wait();
Console.Read();
the first continuation is set to state cancelled (as its TaskContinuationOptions value causes it not to run), without the token being set to cancelled. So the last continuation does run here.
Now I know. The delegates in your task and nested task start right away. The nested task starts waiting 2000 ms, but it is already beyond the point where it checks the cancellation token. The 2000 ms wait in the nested task causes the continuation to cancel, because in the mean time the cancel token is cancelled. However, the continuation of the parent task has no cancellation token, so it always runs.
Edit: the point is that both tasks use the same cancellation token.