Header background

Behind the .NET 4.5 Async Scene: The performance impact of Asynchronous programming in C#

Since .NET version 4.5, the C# language has two new keywords: async and await. The purpose of these new keywords is to support asynchronous programming. This post explains what these two keywords do, what they don’t do, and what the impact of these keywords are on application performance. A little warning: don’t get scared from the IL (Intermediate Code) I include in this post – I think it’s important to understand what the compiler does so that you understand what data your diagnostics tools show you.

What is asynchronous programming?

This term is often confused with multithreading and parallelism, so let’s clear this up!

I think the easiest way to describe asynchronicity is by saying that something is asynchronous when you do not have to stop and wait for it (that is really so easy!).

This does not necessarily require a multithreaded environment.

Imagine that you want to write something to the disk or access data via the network. If you start these operations in a synchronous way, your CPU thread starts the operation and waits until the hard disk, SSD or your network card is done (in the meantime the thread does not do anything, it just waits).

However, if you call an operation in an asynchronous way, the thread which started the operation can do other things.

Multithreading means that multiple execution contexts exist in the application, but this does not say anything about synchronicity. For example, one thread can start another thread and wait (do nothing) until the other one is finished in a completely synchronous fashion, or the other way around.

Parallel computing basically means that multiple calculations are carried out simultaneously. These multiple calculations can be started again either in a synchronous or in an asynchronous way.

Now you can say “Yes, but what if I would like to do something with the result of an asynchronies operation?!” Of course in this case the code which uses the result of the asynchronous operation must run after the operation is done. But this still does not mean that the CPU thread which starts the operation must wait (and do nothing) while the operation is running.

The reason I stress the difference between these 3 concepts is because people often think that the two new keywords have something to do with threading or parallel computing, which is wrong. It is all about asynchronicity!

Async/Await in C#

The two new keywords were introduced to support the above described asynchronous programming model in C# 5 (and in Visual Basic). There are already classes in the .NET framework which are designed with an asynchronous API and support the new keywords. Of course you can create your own async APIs too.

The async keyword is used to decorate methods, lambda expressions and anonym methods. With the async keyword you basically say to the compiler that you want to use await inside that method/lambda expression. Without the async keyword before the method or lambda expression, you cannot use await inside it.

But enough from theory, let’s see some code!

async public static Task GetHttpResponseAsync()

{

using (HttpClient httpClient = new HttpClient())

{

HttpResponseMessage response = await httpClient.GetAsync(“http://en.wikipedia.org/”);

Console.WriteLine(response.Headers);

}

}

In this method we use the .NET 4.5 HttpClient class from the System.Net.Http namespace, which provides functionality to easily send HTTP requests and receive HTTP response. It is a good example of a typical asynchronous API from the .NET framework. The one method we use from this class is GetAsync with a string parameter which basically sends an HTTP GET request to the given address and returns the response (or to be precise it returns a Task referring to the operation which eventually returns the response).

Now let’s see what the code does line by line:

The first line creates the method. With the async keyword we say that we want to use the await keyword inside the method. An async method must be either void or it must return a Task or Task<T>. We use Task here as return type.

The next line creates an HttpClient instance with a using block, so nothing new here.

The third line is the most interesting one! To make this explanation easier let me just rewrite this one line into two equivalent lines:

Original line:

HttpResponseMessage response = await httpClient.GetAsync("http://en.wikipedia.org/");

The semantically equivalent two lines:

Task<HttpResponseMessage> responseTask= httpClient.GetAsync(“http://en.wikipedia.org/”);

HttpResponseMessage response = await responseTask;

The return type of the GetAsync method is Task<HttpResponseMessage>. When you call GetAsync(string) you get back a Task<HttpResponseMessage> which is not the result itself but a representation of the ongoing operation which eventually finishes. So the method call starts the operation and returns immediately with a Task<HttpResponseMessage>.

In the next line with the await keyword I basically say “if the operation is done call the code after the await as a continuation and save the result into the response variable”. But the thread which started the asynchronies operation does not sit and wait to the result (because we called it asynchronously), so it is free to do other work. With the await keyword the calling thread says “Register me for this asynchronous operation, but I’m done here with the method and please inform me if you completed so I can run the rest of the code”. Then the method is called again when the operation is done but this time the thread does not start at the beginning of the method but it jumps right after the await keyword.

And finally when the result is there we print out the header in the last line.

Maybe the word ‘await’ is a little bit misleading, because really no one waits.

To show what the opposite of this asynchronous call is (and what people often mistakenly think async/await does) I would like to show how we can get the same result without the await keyword on a synchronous fashion:

public static void GetHttpResponseAsync()

{

using (HttpClient httpClient = new HttpClient())

{

Task<HttpResponseMessage> responseTask= httpClient.GetAsync(“http://en.wikipedia.org/”);

HttpResponseMessage response = responseTask.Result;

Console.WriteLine(response.Headers);

}

}

Here we use the Result property of the Task<HttpResponseMessage> instance instead of the await keyword (and please note that the async keyword is also removed), which is a blocking call, so the caller thread really waits until the operation is finished and the result is available.

So why is this async/await stuff good?

The big advantage is here that we can avoid threads which are just sitting and waiting for ongoing operations. But the amount of work which your application does is not affected by this.

For example if this is a UI thread it can process other user responses, or if this is an ASP.NET thread pool thread it can process other requests.

Now I also would like to point out that neither the async nor the await keyword starts/stops or creates any thread. Maybe the API which you call with await does, but it does that also without the async and await keywords. With async you just turn this feature for a method or lambda expression on and with the await you subscribe the rest of your code as a callback to an asynchronous call.

So the big takeaway from this part is that when you use the await keyword the thread which starts the operation does not sit and wait until the result is there.

What happens behind the scenes?

Now there are some sentences in the previous sections which may sound a little bit scary, like “the method is called again when the operation is done but this time the thread does not start at the beginning of the method but it jumps right after the await keyword.” In this section, I would like to explain how this is implemented.

The goal here is to give you an idea what the compiled code looks like and how is this “magic” possible which is really not magic, but some transformation by the compiler. Of course there is a lot going on under the hood so I simplify things.

It is very important to know that these two keywords operate on the compiler level. The CLR does not know whether you defined your method with the async keyword or not. Furthermore there is no equivalent of async and await in the Intermediate Language.

This already implies that your code will be significantly transformed by the compiler! To manage the execution of asynchronous calls the compiler turns every async method into a state machine.

Now let’s continue with the GetHttpResponseAsync method and see what the compiler generates from it.

I decompiled the code with the .NET Reflector tool and showed the the C# equivalent of the generated IL code. Let’s examine what the compiler generated!

async 1

As you can see the method header is the same as before the compilation except that the async keyword is gone, but the method body is completely changed. What we have here is just a skeleton method which sets up the state machine and starts it.

The state machine itself is implemented by the <GetHttpResponseAsync>d__2 struct:

async 2

For all the local variables a field was created and there are two methods implemented here from the IAsyncStateMachine interface. Besides that there are some state machine specific fields here like _state.

The most interesting part is the MoveNext() method:

async 3

The original code lives inside this method which is called more than once. Every time this method is called the state machine jumps to the next state and executes some part from the original code depending on its state.

It is not important to understand every line, but there are some really interesting things visible here:

The beginning of our original method is in the default case. But instead of the await keyword we have here a call to the GetAwaiter() method from the Task. In the “if statement” (line 22) we check if the Task is already completed. It is surprising, but sometimes a task finishes immediately or is already cached and in this case we don’t need the continuation, we can just return with the result. If the task is not yet completed we skip that goto statement, the state is set to 0, the awaiter is saved and the state machine is passed to the AwaitUnsafeOnCompleted method and we return.

So at this point the execution hits the line with the return statement and the caller thread is done. This means the thread reached the end of the method (the end of the transformed method) and ready to do something else.

When the task is finished the MoveNext() method is called again as a callback (remember, we passed the state machine to the AwaitUnsafeOnCompleted method!) and this time we are in state 0 so as you see in line 11 we jump out from the case statement, call the GetResult() method on the awaiter and basically run the part of the original method which is after the await keyword. So this is how the thread “skips” the part before the await statement when it is called as a continuation.

As you may already think the number of states in the state machine correlates to the number of await statements in the original code.

So the takeaway from this part is that every async method is transformed into a state machine which has a MoveNext method where every await statement from the original code is represented by one state and this MoveNext method is called every time when a state change occurs.

What is the performance impact of using async/await?

Well, just by counting the number of lines in the original method and in the generated code you already have a feeling that the asynchronicity does not come for free.

As soon as you marked your method with async (regardless if you use await inside it or not) the compiler turns it into a state machine. In an average case when you for example call a web service this overhead is negligible compared to the delay caused by the network communication. But if you write a library or if you write a method which is called millions of times you should think carefully whether you use it or not.

For example because of this transformation your async method cannot be inlined by the compiler.

I created a one line method which just increments an integer variable:

class Program {

static int a = 0;

static void Main(string[] args) {

var start = DateTime.Now;

for (int i = 0; i < 100000; i++) {

TestMethod();

}

var stop = DateTime.Now;

Console.WriteLine(stop – start);

}

public static async void TestMethod() {

a += 1;

}

}

This method with the async keyword took 0.0170119 sec on my machine to execute. When I removed the async keyword it only needed 0.0010006 sec. This means just by adding the async keyword to the method I made my code about 17 times slower. Of course, this is a made up example, since there is really no reason to add async to this method but it still shows the generated overhead.

Conclusion/Wrap up

So the main point of these two new keywords is to support asynchronicity in C# which means that we can avoid threads which don’t do any useful work just sit there and wait for ongoing operations. This is a clever way to make a better use of thread pool threads and to avoid unresponsive UI in a GUI application. The readability of your code will be also definitely better.

On the other hand, you have to be careful when you use these keywords because it adds a lot of boilerplate code which generates overhead.

As we have seen when you use these new keywords your code is significantly changed by the compiler, which introduces new challenges for APM solutions like Dynatrace. Stay tuned for a next post where I show how Dynatrace (download the 15 day free trial) supports these new .NET keywords.