The reason you see Done after Done! due to a common point of confusion with asynchronous methods and has nothing to do with SynchronizationContexts. SynchronizationContext controls which threads work, but async has its own very specific ordering rules. Otherwise, the programs are crazy! :)
'await' ensures that the rest of the code in the current asynchronous method will not be executed until the completed thing completes. This does not promise anything about the caller.
Your async method returns 'void', which is intended for asynchronous methods that do not allow the original caller to rely on the completion of the method. If you want your caller to also wait, you need to make sure your async method returns Task (in case you want to get only completion / exceptions) or Task<T> if you really want to return a value like Well. If you declare the return type of the method as one of two, then the compiler will take care of the rest, creating a task that represents a call to this method.
For instance:
static void Main(string[] args) { Console.WriteLine("A"); // in .NET, Main() must be 'void', and the program terminates after // Main() returns. Thus we have to do an old fashioned Wait() here. OuterAsync().Wait(); Console.WriteLine("K"); Console.ReadKey(); } static async Task OuterAsync() { Console.WriteLine("B"); await MiddleAsync(); Console.WriteLine("J"); } static async Task MiddleAsync() { Console.WriteLine("C"); await InnerAsync(); Console.WriteLine("I"); } static async Task InnerAsync() { Console.WriteLine("D"); await DoSomething(); Console.WriteLine("H"); } private static Task DoSomething() { Console.WriteLine("E"); return Task.Run(() => { Console.WriteLine("F"); for (int i = 1; i < 10; i++) { Thread.Sleep(100); } Console.WriteLine("G"); }); }
In the above code, โAโ through โKโ will be printed in order. Here's what happens:
"A": Before anything else is called
"B": OuterAsync () is called, the Main () function is still waiting.
"C": MiddleAsync () function is called, OuterAsync () is still waiting for MiddleAsync () to complete or not.
"D": InnerAsync () is called, MiddleAsync () is still waiting for InnerAsync () to complete or not.
"E": DoSomething () is called, InnerAsync () is still waiting for DoSomething () to complete or not. It immediately returns a task that starts with parallel .
Due to the parallelism between InnerAsync (), the test for completeness of the task returned by DoSomething () ends and the DoSomething () task actually runs.
As soon as DoSomething () starts, it prints "F", then sleeps for a second.
In the meantime, if thread scheduling is not confused, InnerAsync () will almost certainly now realize that DoSomething () is not yet complete . Asynchronous magic now begins.
InnerAsync () is removed from the stop call and says that its task is incomplete. This causes MiddleAsync () to push itself out of the callstack and says that its own task is incomplete. This causes OuterAsync () to push itself out of the freeze frame and says that its task is also incomplete.
The task returns Main (), which notices its incomplete, and the Wait () call begins.
meanwhile ...
In this parallel thread, the old TPL task created in DoSomething () eventually ends up sleeping. He is typing "G".
Once this task is marked as complete, the rest of InnerAsync () will be assigned to the TPL for re-execution, and it will print "H". This completes the task originally returned by InnerAsync ().
After this task is marked complete, the rest of MiddleAsync () will be assigned to the TPL to restart, and it will print "I". This completes the task originally returned by MiddleAsync ().
After this task is complete, the rest of OuterAsync () will be assigned to the TPL for re-execution, and it will print "J". This completes the task originally returned by OuterAsync ().
Since the OuterAsync () task has completed, the Wait () call returns, and Main () prints "K".
That way, even with a little parallelism in order, asynchronous C # 5 still ensures that the console writes in this exact order.
Let me know if this still seems confusing :)