Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

async and await -Simplified-Internals

0.00/5 (No votes)
5 Mar 2014 1  
async and await- Simplified -Internals

Table of Contents

  • Motivation
  • Prerequisites
  • Introduction
  • How does Async and Await Works
    • Modified Existing Method
      • Instance Creation
      • Initialization of Instance
      • Execution of State Machine
  • Generated State Machine
    • Structure of Generated State Machine
    • Implementation of MoveNext method
  • Conclusion
  • Further Reading
  • References

Motivation

There are plenty and plenty of resources are available on internet of how exactly async and await works internally. I could find many good sites, explains async and await quite extensively, however was more interested to know how exactly Microsoft has implemented async and await. After reading so many articles also there were few unanswered questions like how exactly async and await uses Task. I tried to answer all those questions in this article.

Prerequisites

Usage of async and await are not covered in this article, there are already a lot of good sites are available. In order to grasp the code-snippets prior knowledge of Task, Threading, ThreadPool and of course C# ( :) ) is required.

Introduction

When we use async and await keyword in our code, compile time a structure is generated which is said to be state machine. Why generated structure is termed as state machine, why not Strategy or Proxy? Answer to this question lies in definition of state machine. As per the definition of state machine available on internet

“In general, a state machine is any device that stores the status of something at a given time and can operate on input to change the status and/or cause an action or output to take place for any given change.”

Does generated structure works like this? Answer is Yes. Async/Await keyword supports suspension and resumption model and this is all achieved with the help of state machine. In order to build state machine, compiler does a lot of thing in compiled code. We will explore these things in coming sections.

How does async and await Works:

The best way to understand the internals of async and await is though example. There are plenty of examples available on internet for usage of async and await (and I am not going to waste your time explaining that :) ), however I am taking simple example of async and await. In following example we have defined a method (ReadData) in class (asyncDemo) which accepts file name as a parameter, reads the content of file and display the same on console.

 class asyncDemo
 { 
       public async void ReadData(string fileName)
        {
            UnicodeEncoding uniencoding = new UnicodeEncoding();
            using(FileStream fs = new FileStream(fileName,FileMode.Open))
             {
                byte[] result = new byte[fs.Length];
                await fs.ReadAsync(result, 0, (int) fs.Length);
                Console.WriteLine(uniencoding.GetString(result));
            }
        }
}
Colourised in 6ms

Simple isn’t?? This is simplest source code but what happens internally? What kind of the state machine will be generated for this code and how does the execution will proceed with this? Well, C# compiler is smart enough, when it sees async and awaits keyword; modify the existing source code at compile time. There will be complete overhaul of this code and entirely different MSIL code will be generated. As I keep on saying compiler generates state machine, Yes C# compiler generates the structure called as state machine and modifies ReadData method (which we have defined in source code) to use that state machine. I have organized whole article in two sections one how ReadData method is modified to use state machine and of course second is state machine.

  1. Modified existing Method (in above example ReadData)
  2. Generated State Machine

Modified existing Method

Well, when we re-engineer the compile source code through ILSpy or Reflector, we can see, C# compiler has moved whole program logic into state machine and ReadData method will merely be used for creating, initializing and starting the state machine. First look at the generated code by ILspy.


public void ReadData(string fileName)
{
    asyncDemo.<ReadData>d__0 <ReadData>d__;
    <ReadData>d__.<>4__this = this;
    <ReadData>d__.fileName = fileName;
    <ReadData>d__.<>t__builder = AsyncVoidMethodBuilder.Create();
    <ReadData>d__.<>1__state = -1;
    AsyncVoidMethodBuilder <>t__builder = <ReadData>d__.<>t__builder;
    <>t__builder.Start<asyncDemo.<ReadData>d__0>(ref <ReadData>d__);
}
Colourised in 2ms

This is the re-engineered code of ReadData method. If we generalize the above code (for any kind of method where async or await method is defined), there are some mandatory fields and some are optional. Optional fields can vary based on input variables, local variables and whether method is instance or static. Above code-snippets can be defined in three sections

  1. Instance Creation (<ReadData>d__) of state machine (asyncDemo.<ReadData>d__0 )
  2. Initialization of instance (<ReadData>d__)
  3. Starting the execution of state machine by start method

Step 1: Instance Creation

C# compiler generates the structure (which is in fact contains the actual program logic), is called state machine (in our case it is asyncDemo. <ReadData>d__0). There are lots of fields and methods are generated for state machine, we will go through the state machine structure in Generated State Machine Section. For now to understand we just create the instance of state machine structure in ReadData method.

Step 2: Initialization of Instance

Well once the instance of state machine is created initialization of instance takes place. There are lots of fields which are being initialized in above mentioned code; however interesting fields in state machine structure are state and builder. Remaining fields in State Machine structure are optional for example filename in above code snippets will not exist if there is no input variable, this variable will not be available if Read method is defined as static method.

Now question arrives will state and builder fields of state machine always have same values at creation? Answer is No; state will always be initialized with -1 value at creation however value of builder may differ based on ReadData method return type. In this case ReadData method is returning void that’s the reason we are creating instance of AsyncVoidMethodBuilder.Create(). What if ReadData method returns Task<int> value? Answer is we will create instance of AsyncTaskMethodBuilder<int>.Create(). Have look at modified code:

 public async Task<int> ReadData(string fileName)
 {
            int ts = 0;
            UnicodeEncoding uniencoding = new UnicodeEncoding();
            using (FileStream fs = new FileStream(fileName, FileMode.Open))
            {
                byte[] result = new byte[fs.Length];
                ts = await fs.ReadAsync(result, 0, (int) fs.Length);
                Console.WriteLine(uniencoding.GetString(result));
            }
            return ts;
}
Colourised in 11ms

In above mentioned code snippets we have modified the method to return Task<int> value. There is significant change in builder field of generated compiled code. Instead of calling the AsyncVoidMethodBuilder.Create() method we are calling AsyncTaskMethodBuilder<int>.Create().

public Task<int> ReadData(string fileName)
{
    asyncDemo.<ReadData>d__0 <ReadData>d__;
    <ReadData>d__.<>4__this = this;
    <ReadData>d__.fileName = fileName;
    <ReadData>d__.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
    <ReadData>d__.<>1__state = -1;
    AsyncTaskMethodBuilder<int> <>t__builder = <ReadData>d__.<>t__builder;
    <>t__builder.Start<asyncDemo.<ReadData>d__0>(ref <ReadData>d__);
    return <ReadData>d__.<>t__builder.Task;
Colourised in 3ms

}

What does the Create method of AsyncVoidMethodBuilder class do? Does it only create the instance of builder object and return? Theoretically yes however it uses another redirection. To understand it better let’s see the class diagram how it is implemented .

Let’s understand the Create method of AsyncVoidMethodBuilder class first. Create method returns the instance of self-class (i.e. AsyncVoidMethodBuilder) where it is implemented. Why can’t we create the instance directly instead of calling Create method? Reason is create method allow us to specify synchronization context flag in AsyncVoidMethodBuilder constructor. Lot has been written about the synchronization context, refer to articles in reference section to understand synchronization context better. This is the generated code for Create method of AsyncVoidMethodBuilder class.

public static AsyncVoidMethodBuilder Create()
{
    return new AsyncVoidMethodBuilder(SynchronizationContext.CurrentNoFlow);
}
Colourised in 0ms

As I mentioned earlier AsyncVoidMethodBuilder uses another redirection which is actually responsible for starting the execution of state machine, AsyncMethodBuilderCore structure. Constructor of AsyncVoidMethodBuilder initializes ASyncMethodBuilderCore structure as well.

Step 3 Execution of State Machine:

Once the initialization of the state machine is done execution is started by Start method.

<>t__builder.Start<asyncDemo.<ReadData>d__0>(ref <ReadData>d__);
Colourised in 0ms

If we generalize the above line it will be something like this

Builder.<Generated_StateMachine Structure>(stateMachine Instance);
Colourised in 0ms

Start method is defined in Builder (AsyncVoidMethodBuilder) structure. Start method of builder (AsyncVoidMethodBuilder) structure in turn uses the AsyncMethodBuilderCore structure to start the execution in generated state machine. While calling the AsyncMethodBuilderCore structure start method we pass the instance of state machine. Using the instance of state machine, AsyncMethodBuilderCore structure, calls the method (MoveNext) defined in state machine. So starting point of state machine is nothing but MoveNext method. This diagram explains how the execution started by Start method.

Generated State Machine:

Well, State machine has been invoked by calling MoveNext method defined in state machine. Let’s first understand what is the code generated for state machine at compile time. We have to understand first what all are the methods defined in state machine.

But as of now we are aware that starting point for execution of state machine is nothing but MoveNext method. Major chunk of code, real program logic and suspension and resumption code is included in the MoveNext method of state machine. Before we get into the state machine execution we have to understand the structure of generated state machine.

Structure of Generated state machine :

This is the re-engineered code for initialization of state machine generated by C# complier.

private struct <ReadData>d__0 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncVoidMethodBuilder <>t__builder;
    public asyncDemo <>4__this;
    public string fileName;
    public UnicodeEncoding <uniencoding>5__1;
    public FileStream <fs>5__2;
    public byte[] <result>5__3;
    private TaskAwaiter<int> <>u__$awaiter4;
    private object <>t__stack;  
Colourised in 2ms

In state machine structure, there are few optional fields and some are mandatory fields. Two mandatory fields’ state and builder we have already discussed in “Initialization of Instance” section. State local variable (in state machine structure) is used to hold the state based on which execution takes place. Initially state value is assigned to -1. Remainning fields this, filename, Unicode, filestream and byte are optional, these values vary based on input and local variable. If there is no local and input variable these fields will not exist. Last but not the least awaiter variable in state machine structure is used to implement suspend and resume model. We will look into awaiter variable in following sections.

Next thing is what all are the methods will be implemented by State machine? Or will these also vary based on certain conditions? State machine doesn’t give so much of freedom also :) it always implements two methods MoveNext and SetStateMachine irrespective of number of local variables or input variables. These two methods are forced by IAsyncStateMachine interface which is realized by state machine structure.

Implementation of MoveNext method

MoveNext method is heart of state machine and contains the real program logic to initiate the asynchronous operation; MoveNext method is also responsible to suspend and resume the state machine based on the state. It’s easier to understand how does MoveNext method works once we have looked into the reengineered code of MoveNext method.

void IAsyncStateMachine.MoveNext()
   {
       try
       {
           bool flag = true;
           int num = this.<>1__state;
           if (num != -3)
           {
               if (num != 0)
               {
                   this.<uniencoding>5__1 = new UnicodeEncoding();
                   this.<fs>5__2 = new FileStream(this.fileName, FileMode.Open);
               }
               try
               {
                   num = this.<>1__state;
                   TaskAwaiter<int> taskAwaiter;
                   if (num != 0)
                   {
                       this.<result>5__3 = new byte[this.<fs>5__2.Length];
                       taskAwaiter = this.<fs>5__2.ReadAsync(this.<result>5__3, 0, (int)this.<fs>5__2.Length).GetAwaiter();
                     if (!taskAwaiter.IsCompleted)
                       {
                           this.<>1__state = 0;
                           this.<>u__$awaiter4 = taskAwaiter;
                           this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, asyncDemo.<ReadData>d__0>(ref taskAwaiter, ref this);
                           flag = false;
                           return;
                       }
                   }
                   else
                   {
                       taskAwaiter = this.<>u__$awaiter4;
                       this.<>u__$awaiter4 = default(TaskAwaiter<int>);
                       this.<>1__state = -1;
                   }
                   taskAwaiter.GetResult();
                   taskAwaiter = default(TaskAwaiter<int>);
                   Console.WriteLine(this.<uniencoding>5__1.GetString(this.<result>5__3));
               }
               finally
               {
                   if (flag && this.<fs>5__2 != null)
                   {
                       ((IDisposable)this.<fs>5__2).Dispose();
                   }
               }
           }
       }
       catch (Exception exception)
       {
           this.<>1__state = -2;
           this.<>t__builder.SetException(exception);
           return;
       }
       this.<>1__state = -2;
       this.<>t__builder.SetResult();
   }
Colourised in 43ms

If we see the above generated code, there are two interesting lines to understand. These two lines are majorly responsible to initiate the asynchronous operation and suspend and resume the state machine.

First Line is:

taskAwaiter = this.<fs>5__2.ReadAsync(this.<result>5__3, 0, (int)this.<fs>5__2.Length).GetAwaiter();
Colourised in 0ms

Second Line is:

 this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, asyncDemo.<ReadData>d__0>(ref taskAwaiter, ref this);
Colourised in 1ms

If I reorganize the first line in simple term the whole code can be rewritten like this

taskawaiter = this.filestream.ReadAsync(result,0,Length).GetAwaiter().
Colourised in 0ms

Well in above line we are calling two methods ReadAsync and on return type of ReadAsync method, GetAwaiter method is called. So moral of story first we have to understand the return type of ReadAsync method? Let’s have a look at the class diagram how is ReadAsync method implemented in FileStream class.

As we can see in class diagram ReadAsync method is defined as a virtual method in Stream class and overloaded in FileStream class. In overload implementation of ReadAsync method in FileStream class, ReadAsync method creates and returns the instance of FileStreamReadWriteTask. Eventually FileStreamReadWriteTask class is derived from Task class, so by default it inherits all the properties of Task class. Important point to notice here is return type of ReadAsync method which is nothing but of type FileStreamReadWriteTask class (derived from Task class). Now question remains how does the instance FileStreamReadWriteTask class associate with the asynchronous operation? Answer lies in _asyncResult field defined in FileStreamReadWriteTask class. Importance of __asyncResult property is, it holds the return type of BeginReadAsync method which is nothing but FileStreamAsyncResult. What is the use of BeginReadAsync method? BeginReadAsync is responsible to initiate the asynchronous operation. How exactly task knows when the asynchronous operation is complete? Answer lies in one of the parameter when we call BeginReadAsync method, is asyncCallback. Wait a second from where the asyncCallback has come, when we haven’t passed any callback method? This callback method has been created by the framework itself of delegate type AsyncCallback. This method will be called once the asynchronous operation is completed. Let’s see the declaration of BeginReadAsync method defined by framework.

fileStreamReadWriteTask._asyncResult = this.BeginReadAsync(buffer, offset, count, asyncCallback, fileStreamReadWriteTask);
Colourised in 0ms

So whole philosophy here is async/awaits uses Task and Task use ThreadPoolThread or dedicated thread. Interesting!! How do we get the result back from file stream? It is old traditional method of sending callback method (in this case asyncCallback ) down till driver level (in case of IO operations) and once execution over callback method will be invoked. Questions arrives what if I haven’t sent the callback method? ReadAsync method will create the one for us. In short we just get the task object which wraps the async call back method.

taskawaiter = this.filestream.ReadAsync(result,0,Length).GetAwaiter().
Colourised in 0ms

As we started our discussion with, we call the GetAwaiter method on return type of ReadAsync method. The return type of ReadAsync method is nothing but Task (or its derived class). As a matter of fact we invoke GetAwaiter method defined in Task class. As per the definition of GetAwaiter method on MSDN .

“GetAwaiter method gets an awaiter used to await the task”.

Definition of GetAwaiter method is enough to give the context what is the need to call this method. If I elaborate more, GetAwaiter returns instance of TaskAwaiter class (awaiter) which allows to wait for ReadAsync method completion. However there is much more use of awaiter method, which will explore in next section, for now let’s understand structure of TaskAwaiter class (the instance of which is returned from ReadAsync method) .

Second Line in question was

this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, asyncDemo.<ReadData>d__0>(ref taskAwaiter, ref this);
Colourised in 0ms

Above line is quite cumbersome to understand as it is. If i re-write the above line in simple terms it will be something like this

Builder.AwaitUnsafeOnCompleted(ref taskAwaiter, ref statemachine);
Colourised in 0ms

Basically we are calling AwaitUnsafeOnCompleted method defined in builder instance, passing the taskawaiter instance (received from this.filestream.ReadAsync (result, 0, Length).GetAwaiter() method) and state machine. What is the use of AwaitUnsafeOnCompleted method of builder structure? In AwaiterUnsafeOnCompeleted method, we call UnsafeOnCompleted method defined in awaiter object, passing the callback method (defined in state machine). Reason for passing the callback method (defined in state machine) in UnsafeOnCompleted (defined in TaskAwaiter object) is, to resume the state machine once asynchronous operation completed. In other words, awaiter is waiting for asynchronous operation to complete, once asynchronous operation is completed callback method defined in state machine will be called to resume the state machine and retrieve the result of asynchronous operation. Let’s see the implementation of AwaiUnsafeOnCompleted.

public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine
{
    try
    {
        Action completionAction = this.m_coreState.GetCompletionAction<AsyncVoidMethodBuilder, TStateMachine>(ref this, ref stateMachine);
        awaiter.UnsafeOnCompleted(completionAction);
    }
    catch (Exception exception)
    {
        AsyncMethodBuilderCore.ThrowAsync(exception, null);
    }
} 
Colourised in 3ms

To summarize the whole execution in simple terms based on the state

  1. ReadAsync method will initiate the asynchronous operation and return the Task object.
  2. We call GetAwaiter method defined in Task object (return from step 1) which await until the asynchronous operation is over.
  3. Later we call AwaitUnsafeOnCompleted (defined in builder structure) passing the state machine and awaiter object (returned in step 2).
  4. In AwaiterUnsafeOnCompleted method, UnsafeOnCompleted method of awaiter object will be invoked passing the callback method (defined in state machine).
  5. In Step -4, UnsafeOnCompleted method of awaiter structure is called passing the callback method. Reason is it will allow invoking callback method once the asynchronous operation in step 1 is completed.

Another most important question from above discussion is why did we use UnSafeOnCompleted method defined in awaiter object’s (returned from GetAwaiter method) to resume the state machine? Why not the variations of ContinueWith method of task object? Answer lies in synchronization context. UnSafeOnCompleted uses the current execution context for the task.

Conclusion:

In this article I have taken simple example of async and await where we have just one async and await method. There can be the complex scenario where in single async method multiple await methods are called. In that case reengineered code can have multiple calls to ReadAsync/WriteAsync and AwaitUnsafeOnCompleted methods, instead of if and else we might have switch/case or goto statements. However the processing of GetAwaiter, ReadSync, WriteSync and AwaitUnsafeOnCompleted will still remain the same. The core logic and execution will be same, based on state change there will be call to ReadAsync/WriteAsync, GetAwaiter or AwaitUnsafeOnCompleted methods.

Further Reading:

There are plenty of good article written which explain how Task dispatches the asynchronous request to hardware, what is the importance of synchronous context. Please refer to reference sections to understand more about that.

References:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here