I am really curious how async/await enables your program not to be halted. I really like the way how Stephen Cleary explains async/await: "I like to think of "await" as an "asynchronous wait". That is to say, the async method pauses until the awaitable is complete(so it waits), but the actual thread is not blocked (so it's asynchornous)."
I've read that async method works synchronously till compilator meets await keywords. Well. If compilator cannot figure out awaitable, then compilator queues the awaitable and yield control to the method that called method AccessTheWebAsync
. OK.
Inside the caller (the event handler in this example), the processing pattern continues. The caller might do other work that doesn't depend on the result from AccessTheWebAsync
before awaiting that result, or the caller might await immediately. The event handler is waiting for AccessTheWebAsync
, and AccessTheWebAsync
is waiting for GetStringAsync
. Let's see an msdn example:
async Task<int> AccessTheWebAsync()
{
// You need to add a reference to System.Net.Http to declare client.
HttpClient client = new HttpClient();
// GetStringAsync returns a Task<string>. That means that when you await the
// task you'll get a string (urlContents).
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
// You can do work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork();
// The await operator suspends AccessTheWebAsync.
// - AccessTheWebAsync can't continue until getStringTask is complete.
// - Meanwhile, control returns to the caller of AccessTheWebAsync.
// - Control resumes here when getStringTask is complete.
// - The await operator then retrieves the string result from getStringTask.
string urlContents = await getStringTask;
// The return statement specifies an integer result.
// Any methods that are awaiting AccessTheWebAsync retrieve the length value.
return urlContents.Length;
}
Another article from msdn blog says that async/await does not create new thread or use other threads from thread pool. OK.
My questions:
Where does async/await execute awaitable code(in our example downloading a web site) cause control yields to the next row of code of our program and program just asks result of Task<string> getStringTask
? We know that no new threads, no thread pool are not used.
Am I right in my silly assumption that CLR just switches the current executable code and awaitable part of the method between each other in scope of one thread? But changing the order of addends does not change the sum and UI might be blocked for some unnoticeable time.
The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.
await only blocks the execution of the next lines of code in an async function and doesn't affect the promise execution.
The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.
Await Keyword Basically, it returns to caller thread with reference to ongoing task and stop execution of code below that line and release the current thread to thread pool to process another request. Async and await are always used together, if not, then there will be something wrong.
Where does async/await execute awaitable code(in our example downloading a web site) cause control yields to the next row of code of our program and program just asks result of Task getStringTask? We know that no new threads, no thread pool are not used.
If the operation is truly asynchronous, then there's no code to "execute". You can think of it as all being handled via callbacks; the HTTP request is sent (synchronously) and then the HttpClient
registers a callback that will complete the Task<string>
. When the download completes, the callback is invoked, completing the task. It's a bit more complex than this, but that's the general idea.
I have a blog post that goes into more detail on how asynchronous operations can be threadless.
Am I right in my silly assumption that CLR just switches the current executable code and awaitable part of the method between each other in scope of one thread?
That's a partially true mental model, but it's incomplete. For one thing, when an async
method resumes, its (former) call stack is not resumed along with it. So async
/await
are very different than fibers or co-routines, even though they can be used to accomplish similar things.
Instead of thinking of await
as "switch to other code", think of it as "return an incomplete task". If the calling method also calls await
, then it also returns an incomplete task, etc. Eventually, you'll either return an incomplete task to a framework (e.g., ASP.NET MVC/WebAPI/SignalR, or a unit test runner); or you'll have an async void
method (e.g., UI event handler).
While the operation is in progress, you end up with a "stack" of task objects. Not a real stack, just a dependency tree. Each async
method is represented by a task instance, and they're all waiting for that asynchronous operation to complete.
Where is continuation of awaitable part of method performed?
When awaiting a task, await
will - by default - resume its async
method on a captured context. This context is SynchronizationContext.Current
unless it is null
, in which case it is TaskScheduler.Current
. In practice, this means that an async
method running on a UI thread will resume on that UI thread; an async
method handling an ASP.NET request will resume handling that same ASP.NET request (possibly on a different thread); and in most other cases the async
method will resume on a thread pool thread.
In the example code for your question, GetStringAsync
will return an incomplete task. When the download completes, that task will complete. So, when AccessTheWebAsync
calls await
on that download task, (assuming the download hasn't already finished) it will capture its current context and then return an incomplete task from AccessTheWebAsync
.
When the download task completes, the continuation of AccessTheWebAsync
will be scheduled to that context (UI thread, ASP.NET request, thread pool, ...), and it will extract the Length
of the result while executing in that context. When the AccessTheWebAsync
method returns, it sets the result of the task previously returned from AccessTheWebAsync
. This in turn will resume the next method, etc.
In general the continuation (the part of your method after await
) can run anywhere. In practice it tends to run on the UI thread (e.g. in a Windows application) or on the thread pool (e.g. in an ASP .NET server). It can also run synchronously on the caller thread in some cases ... really it depends on what kind of API you're calling and what synchronization context is being used.
The blog article you linked does not say that continuations are not run on thread pool threads, it merely says that marking a method as async does not magically cause invocations of the method to run on a separate thread or on the thread pool.
That is, they're just trying to tell you that if you have a method void Foo() { Console.WriteLine(); }
, changing that to async Task Foo() { Console.WriteLine(); }
doesn't suddenly cause an invocation of Foo();
to behave any differently at all – it'll still be executed synchronously.
If by "awaitable code" you mean the actual asynchronous operation, then you need to realize that it "executes" outside of the CPU so there's no thread needed and no code to run.
For example when you download a web page, most of the operation happens when your server sends and receives data from the web server. There's no code to execute while this happens. That's the reason you can "take over" the thread and do other stuff (other CPU operations) before awaiting the Task
to get the actual result.
So to your questions:
It "executes" outside of the CPU (so it's not really executed). That could mean the network driver, a remote server, etc. (mostly I/O).
No. Truly asynchronous operations don't need to be executed by the CLR. They are only started and completed in the future.
A simple example is Task.Delay
which creates a task that completes after an interval:
var delay = Task.Delay(TimeSpan.FromSeconds(30));
// do stuff
await delay;
Task.Delay
internally creates and sets a System.Threading.Timer
that will execute a callback after the interval and complete the task. System.Threading.Timer
doesn't need a thread, it uses the system clock. So you have "awaitable code" that "executes" for 30 seconds but nothing actually happens in that time. The operation started and will complete 30 seconds in the future.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With