Your example is rather confusing and hides intuitive behavior, and your expectations are wrong.
Start with a working example:
void Test() { var tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; Task child = null; var parent = Task.Factory.StartNew(() => { child = Task.Factory.StartNew(() => { while (!token.IsCancellationRequested) Thread.Sleep(100); token.ThrowIfCancellationRequested(); }, token, TaskCreationOptions.AttachedToParent, TaskScheduler.Default); while (!token.IsCancellationRequested) Thread.Sleep(100); token.ThrowIfCancellationRequested(); }, token); Thread.Sleep(500); Debug.WriteLine("State of parent before cancel is {0}", parent.Status); Debug.WriteLine("State of child before cancel is {0}", child.Status); tokenSource.Cancel(); Thread.Sleep(500); Debug.WriteLine("State of parent is {0}", parent.Status); Debug.WriteLine("State of child is {0}", child.Status); }
In order for the parent element to be canceled, you need to call somewhere in the body of the parent token.ThrowIfCancellationRequested() . However, just calling token.ThrowIfCancellationRequested() not enough.
You need to conceptualize how parent and child threads flow together to understand why your expectations are wrong.
Main thread: ---\------------------------------------[Cancel]-----/ Parent: \---\-----[Check cancellation]------------------/ Child: \------------------------------[Cancel]---/
As you can see from the above diagram, the parent checks the cancellation method before the cancellation request. The child receives a cancellation signal because he basically waits for the cancellation to be canceled. Now, if you put the same mechanism in the parent, it will receive a cancel signal because it has not finished working before the cancellation is reported.
source share