Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Async

Internal Implementation of Async Methods in .NET [Part 1]

5.00/5 (3 votes)
22 Feb 2015CPL3 min read 16.4K  
Introduction This article is about the internal implementation of await/async functionality. It doesn’t cover the high level functionality, but aims to explore the internal implementation. If you would like to familiarise yourself with the overall functionality first there is an in depth artic

Introduction

This article is about the internal implementation of await/async functionality. It doesn’t cover the high level functionality, but aims to explore the internal implementation. If you would like to familiarise yourself with the overall functionality first there is an in depth article at Microsoft: https://msdn.microsoft.com/en-us/library/hh191443.aspx.

Sample diagram from Microsoft

asyncawait


Since this functionality was released, I was always curious about its internal implementation, this lead me to my forensic investigation and this article. I might have made several mistakes in my conclusions in the article, so any feedback in any form is welcome (even verbal abuse towards me :P).

Most of the conclusions are made by staring for hours at the IL code and trying to make sense of it all. I have included the IL code in case you want to double check my findings.

Overview of Async/Await

The Async functionality is implemented in the C# compiler together with some help from .NET framework base class libraries. The runtime itself didn’t have to be modified, although it’s entirely possible that some tweaks have been made to improve the performance of async/await. In simple terms this actually means that, the same functionality could be written by ourselves with the assistance of some helper classes in the earlier versions of .NET, however it doesn’t provide backwards compatibility.

The Stub Method

When you call an async method the first thing that happens is that its stub is returned. For example for this method

public async Task<string> GetStringAsync(string value)
{
    var result = value.ToUpper();
    await Task.Delay(1000);
    return result;
}

the compiler generates the following code (please see the IL file at the end of the article if you are very curious type):

public Task<string> GetStringAsync(string value) 
{
    AsyncClass.<GetStringAsync>d__0 <GetStringAsync>d__;
    <GetStringAsync>d__.<>4__this = this;
    <GetStringAsync>d__.value = value;
    <GetStringAsync>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
    <GetStringAsync>d__.<>1__state = -1;
    AsyncTaskMethodBuilder<string> <>t__builder = <GetStringAsync>d__.<>t__builder;
    <>t__builder.Start<AsyncClass.<GetStringAsync>d__0>(ref <GetStringAsync>d__);
    return <GetStringAsync>d__.<>t__builder.Task;
}

 

The complete implementation of AsyncTaskMethodBuilder can be viewed here: http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs

 

Here the compiler initialises various variables of the state machine struct and all the magic is implemented in the state machine. Struct is an obvious choice here, we don’t want to create an object on the heap each time the method is invoked, as this would significantly impair the performance of the async methods by creating more work for GC.

The compiler uses unsupported naming conventions to ensure there are no conflicts. However, if we were to fix the variable names, in terms of the actual implementation there is nothing new and the code would actually compile in the previous versions of .NET Framework, if we were to implement the helper classes ourselves.

One other interesting observation is that the keyword async has no effect on how the method is used from the outside. This is one of the reasons why you are cannot put async keyword in interface method definitions and allows you to implement written using previous versions of .NET as long as they return Task, Task<T> or void.

Another thing to remember methods that return void cannot be awaited. So even if your method is not returning anything you should return Task unless you are writing a fire and forget method. The other interesting thing even though we call ToUpper() on the string, this implementation is not included in the stub. The actual implementation has been moved to the state machine.

AsyncClass.il

License

This article, along with any associated source code and files, is licensed under The Common Public License Version 1.0 (CPL)