Multiple Awaits in a single method

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 to SaveAllAsync()?

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?