Stephen Cleary's answer gives the theoretical explanation to this interesting question. My suggestion from a practical standpoint is to output in your experiments the current time and thread-id. It helps a lot at understanding what's going on:
var cts = new CancellationTokenSource();
Print("1");
var task = Task.Run(async () =>
{
Print("2");
try { await Task.Delay(Timeout.Infinite, cts.Token); }
catch (OperationCanceledException) { }
Print("4");
Thread.Sleep(1000);
Print("5");
});
Thread.Sleep(1000);
Print("3");
cts.Cancel();
Print("6");
await task;
...where Print
is this helper method:
static void Print(object value)
{
Console.WriteLine($@"{DateTime.Now:HH:mm:ss.fff} [{Thread.CurrentThread
.ManagedThreadId}] > {value}");
}
Output:
03:11:03.310 [1] > 1
03:11:03.356 [4] > 2
03:11:04.360 [1] > 3
03:11:04.403 [1] > 4
03:11:05.414 [1] > 5
03:11:05.416 [1] > 6
Try it on Fiddle.
As you can see, the continuation after awaiting the Task.Delay
runs on the thread #1, which is the main thread of the console application. It is the same thread that you invoked the cts.Cancel
on.
If you anticipate that the canceling of the CancellationTokenSource
might hijack the current thread for longer than it's desirable, you can offload the canceling to a ThreadPool
thread like this:
await Task.Run(() => cts.Cancel());