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.
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:Task.Delay
internally creates and sets aSystem.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.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 toasync Task Foo() { Console.WriteLine(); }
doesn't suddenly cause an invocation ofFoo();
to behave any differently at all – it'll still be executed synchronously.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 theTask<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.
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. Soasync
/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 callsawait
, 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 anasync 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.When awaiting a task,
await
will - by default - resume itsasync
method on a captured context. This context isSynchronizationContext.Current
unless it isnull
, in which case it isTaskScheduler.Current
. In practice, this means that anasync
method running on a UI thread will resume on that UI thread; anasync
method handling an ASP.NET request will resume handling that same ASP.NET request (possibly on a different thread); and in most other cases theasync
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, whenAccessTheWebAsync
callsawait
on that download task, (assuming the download hasn't already finished) it will capture its current context and then return an incomplete task fromAccessTheWebAsync
.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 theLength
of the result while executing in that context. When theAccessTheWebAsync
method returns, it sets the result of the task previously returned fromAccessTheWebAsync
. This in turn will resume the next method, etc.