Multiple Awaits in a single method
I have a method like this:
public static async Task SaveAllAsync()
{
foreach (var kvp in configurationFileMap)
{
using (XmlWriter xmlWriter = XmlWriter.Create(kvp.Value, XML_WRITER_SETTINGS))
{
FieldInfo[] allPublicFields =
kvp.Key.GetFields(BindingFlags.Public | BindingFlags.Static);
await xmlWriter.WriteStartDocumentAsync();
foreach (FieldInfo fi in allPublicFields)
{
await xmlWriter.WriteStartElementAsync("some", "text", "here");
}
await xmlWriter.WriteEndDocumentAsync();
}
}
}
But I'm struggling to follow what will happen when someone calls SaveAllAsync()
.
What I think will happen is this:
- When someone first calls it,
SaveAllAsync()
will return control to the caller at the lineawait xmlWriter.WriteStartDocumentAsync();
- Then... When they await
SaveAllAsync()
(or wait for the task)... What happens? WillSaveAllAsync()
still be stuck on the first await until that is called? Because there's no threading involved, I guess that is the case...
You can think of await
as "pausing" the async
method until that operation is complete. As a special case, if the operation is already completed (or is extremely fast), then the await
will not "pause" the method; it will continue executing immediately.
So in this case (assuming that WriteStartDocumentAsync
is not already completed), await
will pause the method and return an uncompleted task to the caller. Note that the Task
returned by an async
method represents that method; when the method completes, then that Task
is completed.
Eventually, WriteStartDocumentAsync
will complete, and that will schedule the rest of the async
method to continue running. In this case, it'll execute the next part of the method until the next await
, when it gets paused again, etc. Eventually, the async
method will complete, which will complete the Task
that was returned to represent that method.
For more information, I have an async
/await
intro on my blog.
Stephens answer is of course correct. Here's another way to think about it that might help.
The continuation of a hunk of code is what happens after the code completes. When you hit an await
two things happen. First, the current position in the execution becomes the continuation of the awaited task. Second, control leaves the current method and some other code runs. The other code is maybe the continuation of the first call, or maybe is something else entirely, an event handler, say.
But when the call to
xmlWriter.WriteStartDocumentAsync()
has completed; what happens? Is the current execution interrupted and returned back toSaveAllAsync()
?
It is not clear what you mean by the call "completing". WriteStartDocumentAsync
starts an asynchronous write, probably on an I/O completion thread, and returns you a Task
that represents that asynchronous work. Awaiting that task does two things, like I said. First, the continuation of this task becomes the current position of the code. Second, control leaves the current method and some other code runs. In this case, whatever code called SaveAllAsync
runs the continuation of that call.
Now lets suppose that code -- the caller of SaveAllAsync
continues to run, and lets suppose further that you are in an application with a UI thread, like a Windows Forms application or a WPF application. Now we have two threads: the UI thread and an IO completion thread. The UI thread is running the caller of SaveAllAsync
, which eventually returns, and now the UI thread is just sitting there in a loop handling Windows messages to trigger event handlers.
Eventually the IO completes and the IO completion thread sends a note to the UI thread that says "you can run the continuation of this task now". If the UI thread is busy, that message gets queued up; eventually the UI thread gets to it and invokes the continuation. Control resumes after the first await
, and you enter the loop.
Now WriteStartElementAsync
is invoked. It again starts some code running that depends on something happening on the IO completion thread (presumably; how it does its work is up to it, but this is a reasonable guess), that returns a Task
representing that work, and the UI thread awaits that task. Again, the current position in the execution is signed up as the continuation of that task and control returns to the caller that invoked the first continuation -- namely, the UI thread's event processor. It continues merrily processing messages until one day the IO thread signals it and says that hey, the work you asked for is done on the IO completion thread, please invoke the continuation of this task, and so we go around the loop again...
Make sense?