i saw some post regarding Async and Await usage in this site. few people are saying that Async and Await complete its job on separate background thread means spawn a new background thread and few people are saying no means Async and Await does not start any separate background thread to complete its job.
so anyone just tell me what happen in case of Async and Await when it is used.
here is one small program
class Program
{
static void Main(string[] args)
{
TestAsyncAwaitMethods();
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
public async static void TestAsyncAwaitMethods()
{
await LongRunningMethod();
}
public static async Task<int> LongRunningMethod()
{
Console.WriteLine("Starting Long Running method...");
await Task.Delay(5000);
Console.WriteLine("End Long Running method...");
return 1;
}
}
And the output is:
Starting Long Running method...
Press any key to exit...
End Long Running method...
Need understand two things: a) async/await use tasks(tasks use thread pool) b) async/await is NOT for parallel work.
Just compile this and look at Id's:
Both of the your statements are probably true, but are confusing.
Async-await does usually complete on a separate background thread but it doesn't mean it starts any separate background thread to complete the job.
The point of these asynchronous operations is to to not hold a thread while an asynchronous operation is being executed because true asynchronous operations do not require a thread.
The parts before that operation can be CPU bound and do require a thread and they are executed by the calling thread. The parts after that operation (which is usually called the completion) also require a thread. If there's a
SynchronizationContext
(like there is in UI or asp.net apps) orTaskScheduler
then that part is handled by them. If there isn't any that part is scheduled on theThreadPool
to be executed by an already existing background thread.So, in your example
Task.Delay
creates aTask
that completes after 5 seconds. During that delay there's no need for a thread so you can use async-await.The flow of your example is this: The main thread starts executing
Main
, callsTestAsyncAwaitMethods
, callsLongRunningMethod
, prints the first message, callsTask.Delay
, registers the rest of the method as a continuation to execute after theTask.Delay
completes, return toMain
, print the message and waits synchronously (blocks) onConsole.ReadLine
.After 5 seconds the timer in
Task.Delay
ends and completes theTask
returned fromTask.Delay
. The continuation is then scheduled on theThreadPool
(since it's a console app) and aThreadPool
thread that was assigned that task prints "End Long Running method...".In conclusion, a true asynchronous operation doesn't need a thread to be able to run, but it does need a thread after it has completed which is usually a background thread from the
ThreadPool
but not necessarily.You are asking the wrong question
In effect you are asking, how does a parcel get to my doorstep? By ship or by plane?
The point is that your door step doesn't care wither the parcel was delivered by sea or air.
However the main reason for Microsoft to develop the Task/async/await framework was to take advantage of Event based programming as opposed to Thread based programming.
In general Event based programming is MUCH more efficient and faster than Thread based programming. Which is why most of the .net API uses it. Up until now, however most people avoided Event based programming because it is extremely difficult to understand (again, async/wait was put into place to make this simple).
The problem is that
async/await
is about asynchrony, not threads.If you use
Task.Run
, it will indeed use a background thread (via the Thread Pool, via the Task Parallel Library).However, for IO operations it relies on IO Completion ports to notify when the operation is complete.
The only guarantee
async/await
makes is that when an operation completes, it will return to your caller in the SynchronizationContext that was there when it began. In practical terms, that means it will return on the UI Thread (in a Windows application) or to a thread that can return the HTTP Response (in ASP.NET)Calling await is only possible inside methods marked as async. Once you await a function, the framework knows how to remember your current calling environment and return control to it once the awaited function completes.
You can only ever await functions that return Tasks. So all await deals with is the Task object that gets returned (and until a task is returned, the method you are awaiting is executing synchronously)
To provide you with a Task, the method you are awaiting could spawn a new thread to do it's job, it could synchronously return a completed task with a value (creating a task from a result), it can do whatever it wants. All await does is give the control back to the parent of your function until and unless the Task object you received from the awaitable method is complete. At that point it will continue the execution of your method from the await line.
A simple way to understand what's going on under the hood, is to use SharpLab, if you paste your short example, you'll get how the C# compiler is rewriting your code containing
async
/await
:As pointed in many other answers on SO (like that one), the
async
/await
rewrite the code as a state machine just like for theyield
statement with a method returning eitherIEnumerator
,IEnumerable
,IEnumerator<T>
,IEnumerable<T>
. Except that forasync
methods, you can return either:About the last bullet you can read more about it (the fact that it's pattern based) here and there. This also involves other subtle choices that are out of the scope of your question but you can have a short explanation here about
ValueTask<TResult>
,IValueTaskSource<TResult>
, etc.The act of rewriting of the code is delegated to the compiler, Roslyn is basically using the
AsyncRewriter
class to know how to rewrite the different execution paths, branching to have an equivalent code.In both cases where you have a valid code containing either
yield
orasync
keywords you have an initial state and depending on branching, execution path, theMoveNext()
call that occurs behind the scenes will move from one state to another.Knowing that in the case of a valid
async
code this kind of snippet below:can roughly be translated into (see Dixin's blog for more details):
Bear that in mind that if you have
void
as a return type of anasync
method you won't have muchcurrentTaskToAwait
=]Regarding your code, you can track which thread is (ie. id) used and whether it is from a pool or not:
Will output for example:
You can notice that that the
LongRunningMethod
method terminates after theMain
method, it's due to the fact that you usedvoid
as a return type for asynchronous method. Anasync void
method should only be used for event handlers and nothing else (see Async/Await - Best Practices in Asynchronous Programming)Also, as already mentionned by i3arnon, since no context has been passed, yes the program does (re)use a thread from the thread pool to resume its execution after the async method call.
About those "contexts", I would suggest you to read that article, the article will clarify what is a context, in particular a
SynchronizationContext
.Beware that I said a threadpool thread to "resume" and not to execute the async piece of code, you can find out more about this here.
async
methods are usually designed to leverage whathever latency is inherent to the underlying call, usually IO, eg. writing, reading something on a disk, querying something over the network and so forth.The purpose of truly async methods is to avoid using threads for IO stuff which can help application to scale when you have a lot more requests. Typically can handle more requests in ASP.NET WebAPI with
async
resources since each of them will "free" the thread of the request whenever they will hit the database or whatheverasync
-able calls you are making in that resource.I suggest you to read the answers of that question
A way to avoid this is to leverage a C# 7.1 feature and expect a
Task
as a return type instead of thevoid
:You'll then get
Which looks more like what you would normally expect.
More resources about
async
/await
:async
/await
(1) Compilationasync
/await
(2) Awaitable-Awaiter Patternasync
/await
(3) Runtime Contextasync
andawait
ExecutionContext
vsSynchronizationContext