Home > Software engineering >  Use of await call after CancellationTokenSource cancelled affects execution order?
Use of await call after CancellationTokenSource cancelled affects execution order?

Time:04-14

Basically, why does switching out the Thread.Sleep() line for the await Task.Delay() line cause the output order to reverse?

var cts = new CancellationTokenSource();

var task = Task.Run(async () =>
{
    try
    {
        await Task.Delay(TimeSpan.FromSeconds(30), cts.Token);
    }
    catch (TaskCanceledException) { }
    Thread.Sleep(TimeSpan.FromSeconds(3)); // output 1 2
    //await Task.Delay(TimeSpan.FromSeconds(3)); // outputs 2 1
    Console.WriteLine("1");
});

Thread.Sleep(TimeSpan.FromSeconds(3));

cts.Cancel();
Console.WriteLine("2");

await task;

.NET Fiddle: https://dotnetfiddle.net/nqX5LP

CodePudding user response:

I think this is an interesting question and highlights the difference between Tasks and Threads. See here for a good description: What is the difference between task and thread?

The highlight is that the app runs on a Thread. Tasks need a thread to execute, but not every task needs its own thread. The behavior you're noticing is due to each task likely being run on the same thread.

Why would that have an impact?

If you look at the documentation for Thread.Sleep() you'll see the note that it

Suspends the current thread for the specified amount of time (https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread.sleep?view=net-6.0).

So in the case of using Thread.Sleep() inside the task the flow looks like the following:

  1. Start task
  2. await Delay for 30 seconds
  3. Sleep for 3 seconds (blocks entire thread, including task)
  4. Cancel token
  5. Cancel token exception thrown and handled
  6. Task sleeps for 3 seconds (blocks entire thread, including outer task)
  7. Task finishes sleep and outputs 1
  8. Main task outputs 2
  9. await task which has already completed
  10. fin

When using Task.Delay, which is a "sleep" that only blocks the task, not the executing thread:

  1. Start task
  2. await delay for 30 seconds
  3. Main task sleeps for 3 seconds (blocking entire thread, including task)
  4. Cancel token
  5. Cancel token exception thrown and handled
  6. await delay for 3 seconds (gives control back to main task)
  7. Output 2
  8. await task
  9. Output 1
  10. fin

CodePudding user response:

CancellationTokenSource.Cancel executes the cancellation callbacks synchronously. This includes async method continuations because await uses TaskContinuationOptions.ExecuteSynchronously (as described on my blog). Which execute synchronously except when they don't.

  • Related