Does .NET resume an await continuation on a new different thread pool thread or reuse the thread from a previous resumption?
Let\'s image below C# code in a .NET Core co
Does .NET resume an await continuation on a new different thread pool thread or reuse the thread from a previous resumption?
Most of the time it will use the same thread, but this is not guaranteed. This answer to a question where the OP wants to force it to the same thread gives some details.
Where the continuation is run is up to the TaskScheduler. I haven't looked, but I imagine it will run the continuation on the same thread just to avoid unnecessary overhead when working with the thread pool.
Is there any possibility each continuation will resume on a different thread pool thread like below?
Yes, there's a possibility, but probable that you won't see this, again because of the scheduler. You'd probably have to write your own to force a difference, and personally I don't know why you'd do that. (Not saying that was your intent).
Does .NET resume an await continuation on a new different thread pool thread or reuse the thread from a previous resumption?
Neither. By default, when await
ing Task
s, await will capture a "context" and use that to resume the asynchronous method. This "context" is SynchronizationContext.Current
, unless it is null
, in which case the context is TaskScheduler.Current
. In your example code, the context is the thread pool context.
The other part of the puzzle is undocumented: await uses the TaskContinuationOptions.ExecuteSynchronously flag. This means that when the Task.Run
task is completed (by thread 4
), its continuations are run immediately and synchronously - if possible. In your example code, the continuation may run synchronously because there's enough stack on thread 4
and the continuation should be run on a thread pool thread and thread 4
is a thread pool thread.
Likewise, when AsyncThree
completes, the continuation for AsyncTwo
is run immediately and synchronously - again on thread 4
since it meets all the criteria.
This is an optimization that is especially helpful in scenarios like ASP.NET, where it's common to have a chain of async
methods and have one task completing (e.g., a db read) that completes the entire chain and sends the response. In those cases you want to avoid unnecessary thread switches.
An interesting side effect of this is that you end up with an "inverted call stack" of sorts: the thread pool thread 4
ran your code and then completed AsyncThree
and then AsyncTwo
and then AsyncOne
, and each of those completions are on the actual call stack. If you place a breakpoint on the WriteLine
in AsyncOne
(and look at external code), you can see where ThreadPoolWorkQueue.Dispatch
(indirectly) called AsyncThree
which (indirectly) called AsyncTwo
which (indirectly) called AsyncOne
.
As you can see Why is the initial thread not used on the code after the awaited method? it is quite possible to resume on another thread, based on what is available at the moment.
In asynchronous programming there is not definite usage of specific threads when used with async await. You only know that an available thread will be picked from the thread pool.
In your case, since the execution is pretty much sequential, the thread is freed and you get the number 4.
Based on the thread pool documentation https://docs.microsoft.com/en-us/dotnet/standard/threading/the-managed-thread-pool the thread pool is unique per process, so I'd expect the usage of the first available thread to be used. So is you have no other concurrent operations, the thread 4 will be reused each time. There are no guarantees though.
There is no real async call in your sample code ie. An i/o call so most likely the continuation is inlined on the original thread as an optimization. In your async methods if you add await Task.Delay you may observe continuation may run on a different thread. Bottomline never make any assumptions on which thread the continuations run, assume it gets run on another thread.