Motivation for async/await in C#

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:

  1. Task.Delay(15000) executes, returning a Task representing it. It does not block since it does not necessarily execute on the same thread.
  2. 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.
  3. Eventually, the Task finishes executing. The await 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.

9 thoughts on “Motivation for async/await in C#”

  1. You should mention that a server has only so many cores and if it’s serving 1000s of concurrent requests then thread contention (blocking IO) is a death sentence. Same goes for batch processing or multiple services occupying the same hardware.

  2. I’m new to async and please forgive my ignorance.
    Q: If you don’t await the Async method, it will behave as a synchronous method?

    1. No. It will be fired off separately, and in the meantime, the method will continue executing. Since you don’t await it, you’ll never know whether and when it completed. You can do this only for fire-and-forget tasks where you don’t care about and don’t have to do anything with the result.

        1. It is not blocked; GetAsync() is an asynchronous operation. The UI thread blocks if you do a blocking operation such as GetAsync().Wait(), or Thread.Sleep().

          The best way to learn this is to experiment a little and see the effects for yourself. 🙂

          1. Thanks for clarifying, I had done that but, missed the “.Result()” was playing the culprit 🙂

      1. That is not correct. In absence of await it will behave as synchronous.

        Run the following and view the console output
        In main,

        var a1 = myobject.TestSync(“http://www.dotnetfoundation.org”);
        Console.WriteLine(“Parent: after sync call!”);
        var a2 = myobject.TestAsync(“http://www.dotnetfoundation.org”);
        Console.WriteLine(“Parent: after async call!”);

        Call the methods below
        public int TestSync(string url)
        {
        HttpClient client = new HttpClient();
        Console.WriteLine($”Child: before sync call! {url}”);
        var response = client.GetStringAsync(url).Result;
        Console.WriteLine($”Child: after sync call! {url} – length: {response.Length}”);
        return response.Length;
        }

        public async Task TestAsync(string url)
        {
        HttpClient client = new HttpClient();
        Console.WriteLine($”Child: before async call! {url}”);
        var response = await client.GetStringAsync(url);
        Console.WriteLine($”Child: after async call! {url} – length: {response.Length}”);
        return response.Length;
        }

      2. That is not correct. In absence of await it will behave as synchronous.

        Run the following and view the console output
        In main,

        var a1 = myobject.TestSync(“http://www.dotnetfoundation.org”);
        Console.WriteLine(“Parent: after sync call!”);
        var a2 = myobject.TestAsync(“http://www.dotnetfoundation.org”);
        Console.WriteLine(“Parent: after async call!”);

        Call the methods below
        public int TestSync(string url)
        {
        HttpClient client = new HttpClient();
        Console.WriteLine($”Child: before sync call! {url}”);
        var response = client.GetStringAsync(url).Result;
        Console.WriteLine($”Child: after sync call! {url} – length: {response.Length}”);
        return response.Length;
        }

        public async Task TestAsync(string url)
        {
        HttpClient client = new HttpClient();
        Console.WriteLine($”Child: before async call! {url}”);
        var response = await client.GetStringAsync(url);
        Console.WriteLine($”Child: after async call! {url} – length: {response.Length}”);
        return response.Length;
        }

Leave a Reply

Your email address will not be published. Required fields are marked *