Multiple Awaits in a single method

C#AsynchronousAsync Await

C# Problem Overview


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:

  1. When someone first calls it, SaveAllAsync() will return control to the caller at the line await xmlWriter.WriteStartDocumentAsync();
  2. Then... When they await SaveAllAsync() (or wait for the task)... What happens? Will SaveAllAsync() still be stuck on the first await until that is called? Because there's no threading involved, I guess that is the case...

C# Solutions


Solution 1 - C#

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.

Solution 2 - C#

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?

Solution 3 - C#

Whenever a function is 'async' it means that when an 'await' is done on a System.Threading.Tasks.Task a two main things happen:

  1. The current position in the execution becomes a "continuation" of the awaited Task, meaning that after the Task is completed it will do what is necessary to make sure the remainder of the async method gets called. This work could be be done in a specific thread, some random thread pool thread or maybe the UI thread, this depends on the SynchronizationContext that the Task gets for its "continuation".

  2. The control is returned to either:

  • If it is the first await in the async method it returns to the original calling method with a System.Threading.Tasks.Task that represents the async method (NOT any of the Tasks created within the async method). It can just ignore it and carry on, wait for it with a Task.Result/Task.Wait() (careful you don't block the UI thread) or even do an await on it if it itself is an async method.

  • If it is not the first await in the async method it will just return to whichever handler in whichever thread was executing the "continuation" of the last Task that was awaited.

So to answer:

> When they await SaveAllAsync() (or wait for the task)... What happens?

Doing await SaveAllAsync() won't NECESSARILY cause it to get stuck on any of it's internal awaits. This is because an await on SaveAllAsync() just drops back to the caller of whichever method called SaveAllAsync() which can carry on like nothing happened, just like SaveAllAsync()'s internal awaits dropped back to it. This keeps the thread moving and able to respond (potentially) to the request at some later time to run the "continuation" of SaveAllAsync()'s first internal await: await xmlWriter.WriteStartDocumentAsync()'. This way, bit by bit, SaveAllAsync() will eventually finish and nothing will get stuck.

BUT... if you OR some other code deeper down ever does a Task.Result/Task.Wait() on any of the Tasks returned by an await this could cause things to get stuck if the "continuation" tries to run on the same thread as the waiting code.

Solution 4 - C#

A simple answer using parent method example:

await SaveAllAsync();
string x = ""; // <- this will run only when SaveAllAsync() is done including all awaits

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionXenoprimateView Question on Stackoverflow
Solution 1 - C#Stephen ClearyView Answer on Stackoverflow
Solution 2 - C#Eric LippertView Answer on Stackoverflow
Solution 3 - C#simon hearnView Answer on Stackoverflow
Solution 4 - C#dkostasView Answer on Stackoverflow