Introduction
In .NET 4.5, the async
and await
keywords were introduced. They are now a welcome and ubiquitous part of the .NET developer’s toolkit. However, I often run into developers who are not familiar with this concept, and I have had to explain async
/await
many times. Thus, I felt it would make sense to write up this explanation so that it can be accessible to all.
async
/await
is really just syntactic sugar for task continuations, but it makes asynchronous, I/O-bound code very elegant to work with. In fact, it is so popular that it has been spreading to other languages such as JavaScript.
As far as I can remember, async
/await
was introduced mainly to facilitate building Windows Phone applications. Unlike desktop applications, Windows Phone had introduced a hard limit in which no method could block for more than 50ms. Thus it was no longer possible for developers to resort to blocking their UI, and the need arose to use asynchronous paradigms, for which tasks are a very powerful, but sometimes tedious, option. async
/await
makes asynchronous code look almost identical to synchronous code.
In this article, I will not go into detail about using Tasks in .NET (which deserves a whole series of articles in itself), but I will give a very basic explanation on how to use them for asynchronous processing. More advanced usage is left for future articles.
Sidenote: Rejoice, for this is the 200th article published on Gigi Labs!
Motivation
Although async
/await
can be used in all sorts of applications, I like to use WPF applications to show why it’s important. You don’t need to know WPF for this; just follow along.
Let’s create a new WPF application, and simply drag a button from the Toolbox onto the WPF window:
Double-clicking that button will cause a click event handler to be generated in the codebehind class (MainWindow.xaml.cs):
private void Button_Click(object sender, RoutedEventArgs e) { }
Now, let’s be evil and toss a Thread.Sleep()
in there:
private void Button_Click(object sender, RoutedEventArgs e) { Thread.Sleep(15000); }
Run the application without debugging (Ctrl+F5) and click the button. Try to interact with the window (e.g. drag it around). What happens?
The UI is completely frozen; you can’t click the button again, drag the window, or close it. That’s because we’ve done something very, very stupid. We have blocked the application’s UI thread.
Let us now replace the Thread.Sleep()
with this Task.Delay()
instead (notice there is also that async
in the method signature, and it’s important):
private async void Button_Click(object sender, RoutedEventArgs e) { await Task.Delay(15000); }
If you run the application now, you’ll find that you can interact with the window after clicking the button, even though the code appears to be doing practically the same thing as before. So what changed?
Image taken from: Asynchronous Programming with Async and Await (C# and Visual Basic)
The way control flow works with async
/await
is explained in detail on MSDN, but it may feel a little overwhelming if you’re learning this the first time. Essentially, to understand what is happening, we need to break that await Task.Delay(15000)
line into the following steps:
Task.Delay(15000)
executes, returning aTask
representing it. It does not block since it does not necessarily execute on the same thread.- Because of the
await
, execution of the remainder of the method is temporarily suspended. In terms of method execution, it’s as if we’re blocking. - Eventually, the
Task
finishes executing. Theawait
part is fulfilled, and the remainder of the method can resume execution.
async/await with HttpClient
Using a simple delay is a nice way to get an initial feel of async
/await
, but it is also not very realistic. async
/await
works best when you are dealing with I/O requests such as sockets, databases, NoSQL storage, files, REST, etc. To this effect, let’s adapt our example to use an HttpClient
.
First, let’s install the following package via NuGet:
Install-Package Microsoft.Net.Http
We’re going to repeat the same experiment as before: first we’ll do a silly blocking implementation, and then we’ll make it asynchronous.
Let’s work with this code that gets the response from the Google homepage:
private void Button_Click(object sender, RoutedEventArgs e) { var baseAddress = new Uri("http://www.google.com"); using (var httpClient = new HttpClient() { BaseAddress = baseAddress }) { var response = httpClient.GetAsync("/").Result; var content = response.Content.ReadAsStringAsync().Result; } }
If we run this and click the button, the window blocks for a fraction of a second, and we get back the HTML from Google:
Google has a very fast response time, so it is a poor example for visualising the problems of blocking code in a UI. Instead, let’s use a website that takes a very long time to load. One that I’ve written about before is that of the Malta Tourism Authority, which takes over 20 seconds to load. Let’s change our base address in the code:
var baseAddress = new Uri("http://mta.com.mt");
If you run the application now and click the button, you’ll see that it will be stuck for a fairly long time, just like before. We can sort this out by making the request asynchronous. To do that, we replace each .Result
property with an await
. In order to use await
, we have to mark the method as async
. In order to get a notification when the response actually comes back, I’ll also toss in a message box:
private async void Button_Click(object sender, RoutedEventArgs e) { var baseAddress = new Uri("http://mta.com.mt"); using (var httpClient = new HttpClient() { BaseAddress = baseAddress }) { var response = await httpClient.GetAsync("/"); var content = await response.Content.ReadAsStringAsync(); MessageBox.Show("Response arrived!", "Slow website"); } }
Run the application and click the button. You can interact with the window while the request is processed, and when the response comes back, you’ll get a notification:
If you put a breakpoint and follow along line by line, you’ll see that everything happens sequentially: the content
line will not execute before the response
has arrived. So async
/await
is really just giving us the means to make asynchronous code look very similar to normal sequential execution logic, hiding the underlying complexities. However, as we’ll see in later articles, it is also possible to use this abstraction for more powerful scenarios such as parallel task execution.
The Importance of Asynchronous Code
While I have not really explained how to work with async/await
in any reasonable depth yet, the importance should now be evident: making I/O calls asynchronous means that your thread does not need to block, and can be freed to do other work. While this has a great impact on user experience in GUI applications, it is also fundamental in other applications such as APIs. Under high load, it is possible to end up with thread starvation when blocking, because all threads are stuck waiting for I/O, when they could otherwise be processing requests.
For pure asynchronous code, blocking isn’t actually ever necessary. When an I/O request such as HttpClient.GetAsync()
is fired, .NET uses something called I/O Completion Ports (IOCP) which can monitor incoming I/O and trigger the caller to resume when ready.
We have merely scratched the surface here. There is a lot to be said about async
/await
, and likewise there are a lot of pitfalls that one must be made aware of. This article has shown why asynchronous code is important, and future articles may cover different aspects in more detail.