I was reading about async/await recently and I am confused with the fact that many of the articles/posts I was reading state that new thread is not created when using async await (Example).
I have created a simple console app to test it
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);
MainAsync(args).Wait();
Console.WriteLine("Main End: " + Thread.CurrentThread.ManagedThreadId);
Console.ReadKey();
}
static async Task MainAsync(string[] args)
{
Console.WriteLine("Main Async: " + Thread.CurrentThread.ManagedThreadId);
await thisIsAsync();
}
private static async Task thisIsAsync()
{
Console.WriteLine("thisIsAsyncStart: " + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1);
Console.WriteLine("thisIsAsyncEnd: " + Thread.CurrentThread.ManagedThreadId);
}
}
Output of the following code is:
Main: 8
Main Async: 8
thisIsAsyncStart: 8
thisIsAsyncEnd: 9
Main End: 8
Am I missing the point, or thisIsAsyncEnd is having different thread ID than other actions?
EDIT:
I have updated code as suggested in the answer below to await Task.Delay(1)
, but I am still seeing the same results.
Quote from the answer below:
Rather, it enables the method to be split into multiple pieces, some of which may run asynchronously
I want to know where does the asynchronously
part run, if there are no other threads created?
If it runs on the same thread, shouldn't it block it due to long I/O request, or compiler is smart enough to move that action to another thread if it takes too long, and a new thread is used after all?
I recommend you read my
async
intro post for an understanding of theasync
andawait
keywords. In particular,await
(by default) will capture a "context" and use that context to resume its asynchronous method. This "context" is the currentSynchronizationContext
(orTaskScheduler
, if there is noSynchronzationContext
).As I explain on my blog, truly asynchronous operations do not "run" anywhere. In this particular case (
Task.Delay(1)
), the asynchronous operation is based off a timer, not a thread blocked somewhere doing aThread.Sleep
. Most I/O is done the same way.HttpClient.GetAsync
for example, is based off overlapped (asynchronous) I/O, not a thread blocked somewhere waiting for the HTTP download to complete.Once you understand how
await
uses its context, walking through the original code is easier:Main
and callsMainAsync
.MainAsync
and callsthisIsAsync
.thisIsAsync
and callsTask.Delay
.Task.Delay
does its thing - starting a timer and whatnot - and returns an incomplete task (note thatTask.Delay(0)
would return a completed task, which alters the behavior).thisIsAsync
and awaits the task returned fromTask.Delay
. Since the task is incomplete, it returns an incomplete task fromthisIsAsync
.MainAsync
and awaits the task returned fromthisIsAsync
. Since the task is incomplete, it returns an incomplete task fromMainAsync
.Main
and callsWait
on the task returned fromMainAsync
. This will block the main thread untilMainAsync
completes.Task.Delay
goes off,thisIsAsync
will continue executing. Since there is noSynchronizationContext
orTaskScheduler
captured by thatawait
, it resumes executing on a thread pool thread.thisIsAsync
, which completes its task.MainAsync
continues executing. Since there is no context captured by thatawait
, it resumes executing on a thread pool thread (actually the same thread that was runningthisIsAsync
).MainAsync
, which completes its task.Wait
and continues executing theMain
method. The thread pool thread used to continuethisIsAsync
andMainAsync
is no longer needed and returns to the thread pool.The important takeaway here is that the thread pool is used because there's no context. It is not automagically used "when necessary". If you were to run the same
MainAsync
/thisIsAsync
code inside a GUI application, then you would see very different thread usage: UI threads have aSynchronizationContext
that schedules continuations back onto the UI thread, so all the methods will resume on that same UI thread.I wondered exactly the same. For me the explanations by MSDN were contradictory:
MSDN: Asynchronous Programming with async and await
await (C#-Referenz)
I didn't understand how the original thread could be not blocked, without the use of additional threads. Additionally the "invoke" wording suggested that there are multiple threads used somewhere and somehow.
But then I realized, that everything is written correctly, that there aren't used any other threads by these keywords. It is by design of the
Task
class to provide mechanisms that may use different threads - or not.While stephen-cleary beautifully explained these mechanisms for the
Task.Delay()
method, I extended the MSDN example to learn howawait
behaves withTask.Run()
:As you can see from the output, there are multiple threads used - not by
async/await
, but byTask.Run()
:This is as usual as it ever have been, but me personally needed this explicit example to understand what's going on and to separate what's done by
async/await
and what's done byTask
.Very good explanation for your question is here https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/
Making the method with
async
don't mean that it will create another thread.If the RunTime is seeing that your method which is called withawait
in yourasync
method is delayed, it is exiting from that method and waits after awaited methods finish and then continue that method with another thread.Try to change yourTask.Delay(2000)
toTask.Delay(0)
and you will see that it doesn't create a new Thread.The RunTime will count it, if it needs to create it will create if not - not.I tried your example with 0 ms and get all the same thread:
Taken from Stephen Toub's blog: