How "await" works

A while ago, marc wrote about our “await” keyword added in Oxygene 5.1. “Await” is a new language element for the .NET 4.5 framework that essentially lets you write sequential code without blocking the main thread. It’s actually pretty amazing, in that the compiler will split up your code so the asynchronous parts can run in a thread and then return execution to the main thread when it’s done. A simple example of this would be to do an HTTP request to our blog to grab the lastBuildDate field (the last time a new item was posted or updated).

The code you would write for that could look something like this:

method MainForm.button1_Click(sender: System.Object; e: System.EventArgs);beginvar lClient :=new HttpClient();{ a }var lResponse := await lClient.GetAsync('http://blogs.remobjects.com/feed');{ b } MessageBox.Show( String.Format('The RemObjects blog was last updated at {0}', XDocument.Parse(await lResponse.Content.ReadAsStringAsync).Descendants('lastBuildDate').FirstOrDefault():Value));end;

There are actually two awaits in this block. The first one waits for the return of the HTTP request itself, the second one for the body.

If you wrote something similar without await and using synchronous APIs, your application would block and appear hung when the user clicks the button, since both parts of this request can take a while to execute. Pre-“await”, you could/should have opted to use existing asynchronous APIs, but they would have required to write code that is much more convoluted, involving anonymous methods and callbacks.

“Await” lets you write this code the “naïve” way, as simple, straight-forward sequential actions, and the compiler takes care of making things run in separate threads for you.

What really happens here is this (reformatted the names for readability):

method MainForm.button1Click(sender: System.Object; e: System.EventArgs);beginvar worker :=new MainForm.button1Click_worker(sender, e,self); worker.Run;// your Button Click event is done here and your application is responsive right away.end;

The “Run” method (actually called “d__0″) contains a state machine, which looks like it is a large “case” statement (again, slightly reformatted to make it more readable):

method MainForm.Button1Clickworker.Run;beginvar lBeforeFirstAwait :=false;trycase fState of0:begin fState :=-1;// If an exception occurs, we should never return here. lBeforeFirstAwait :=true; lClient =new HttpClient();{ a } fAwaiter :=self.Client.GetAsync('http://blogs.remobjects.com/feed').GetAwaiter();{ begin of b }if fAwaiter.IsCompletethenbegin{pseudo} goto case element 1;end; fState :=1;// It's not complete; this will make sure we jump to // case element 1 when it is done. The awaiter // will make sure it returns to the main thread. fAwaiter.OnComplete(@Run);exit;end;1:begin// At this point we have the result of the HTTP request. fState :=-1; lResponse := TaskAwaiter(fAwaiter).GetResult();{ end of b }// Response is in. Here it pushes all values on the stack (before the next// await) and saves them in a tuple.   fStackAtThisPoint := Tuple.Create('The RemObjects blog was last updated at {0}');// And waits again fAwaiter := lResponse.Content.ReadAsStringAsync().GetAwaiter();if fAwaiter.IsCompletethenbegin{pseudo} goto case element 2;end;   fState :=2;// next time it jumps to the 2nd case element. fAwaiter.OnComplete(@Run);exit;end;2:begin// At this point we have the result of ReadOfStringAsync. fState :=-1; MessageBox.Show( String.Format( Tuple(fStackAtThisPoint).Item1,// restore the stack as it was before the await (just a string) XDocument.Parse(TaskAwaiter(fAwaiter).GetResponse()).Descendants('lastBuildDate').FirstOrDefault():Value ));   Builder.SetResult;// It's done. The builder is used to// turn this into a Task, if the outside method returns one.end;end;excepton e: Exception dobeginif lBeforeFirstAwait thenraise;// If it fails before the first await// it should be returned like a regular exception. Builder.SetException(e);// else put it in the builder so a waiting task// gets the exception.end;end;

The nice part about this is that it all happens behind the scenes: the compiler turns the original method into a state machine, calls the right things and splits it up, making the original code retain its simplicity both in code and in reading. It’s a language feature (assisted by type support in the underlying runtime and framework) that is pretty unique, and the simplicity it provides is impossible to achieve without deep language integration.

Carlo Kok

Read more posts by this author.