Understanding the Async Await API – part 2 (.NET)

Introduction:

 

In Part 1  of this article I explained the Await API and how it works in general. This part goes deeper, as we dig into the state machine and understand its inner workings.

 

We will do this by using ILSpy with the option “Decompile async methods” disabled. (You can also get this with Red Gate Reflector set to .net 4.0)

In case you want to see this for yourself, and you have a new ILSpy which supports .net 4.5, remove the check from the box in the ILSpy options dialog:

 

ilspyOptions.png

But that won’t be necessary since all the code is already decompiled below.

 

The samples

I decoded two async functions:

The first function is simple, and uses ”await” once in order to get a string from an http server:

 

First function: 

// AsyncAwait.Engine.Downloader
public static async Task<string> DownloadHtmlAsyncTask(string url)
{
    HttpClient httpClient = new HttpClient();
    Debug.WriteLine("before await");
    string result = await httpClient.GetStringAsync(url);
    Debug.WriteLine("after await");
    return result;
}

 This code can be simply broken into two parts: “before” and “after”, as the WriteLines indicate.

 

The second function is more complex, as it uses the first function twice in succession, so it will get cut into three fragments: ”before1”, “before2”, and “end”. As a result,the surrounding logic will be a bit different since the code generation is changed when going from 2 fragments to 3 or more.

 

 Second function: 

// AsyncAwait.ViewModel.MainWndViewModel
private async Task<string> DownloadWithUrlTrackingTaskAsync(string url)
{
    Debug.WriteLine("before await1");
    string Data = await DownloadHtmlAsyncTask(url);
    Debug.WriteLine("before await2");
    string Data1 = await DownloadHtmlAsyncTask(url);
    Debug.WriteLine("the end.");
    return Data;
}

 Let’s look into the code generated for the first function:

 

Decoding the first function:

 

DownloadHtmlAsyncTask uses only one await call.

This is the calling code which initializes the state machine:

 

Calling code 1st function:

[DebuggerStepThrough, AsyncStateMachine(typeof(AsyncMethods.<DownloadHtmlAsyncTask>d__0))]
public static Task<string> DownloadHtmlAsyncTask(string url)
{
    AsyncMethods.<DownloadHtmlAsyncTask>d__0 <DownloadHtmlAsyncTask>d__;
    <DownloadHtmlAsyncTask>d__.url = url;
    <DownloadHtmlAsyncTask>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();

    //set initial machine state to -1
    <DownloadHtmlAsyncTask>d__.<>1__state = -1;
    AsyncTaskMethodBuilder<string> <>t__builder = <DownloadHtmlAsyncTask>d__.<>t__builder;
    <>t__builder.Start<AsyncMethods.<DownloadHtmlAsyncTask>d__0>(ref <DownloadHtmlAsyncTask>d__);
    return <DownloadHtmlAsyncTask>d__.<>t__builder.get_Task();
}

 

The state machine definition: (with my comments inline)

 

State machine code 1st function:

using ConsoleApplication1;
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[CompilerGenerated]
[StructLayout(LayoutKind.Auto)]
private struct <DownloadHtmlAsyncTask>d__0 : IAsyncStateMachine
{
    //initial state is set to -1 by the machine's creation code
    public int <>1__state;
    public AsyncTaskMethodBuilder<string> <>t__builder;
    public string url;
    public HttpClient <httpClient>5__1;
    public string <result>5__2;
    private TaskAwaiter<string> <>u__$awaiter3;
    private object <>t__stack;
    void IAsyncStateMachine.MoveNext()
    {
        string result;
        try
        {
            int num = this.<>1__state;

            //if (!Stop)
            if (num != -3)
            {
                TaskAwaiter<string> taskAwaiter;
                //machine starts with num=-1 so we enter
                if (num != 0)
                {
                    //first (+ initial) state code, run code before await is invoked
                    this.<httpClient>5__1 = new HttpClient();
                    Debug.WriteLine("before await");
                    
                    //a task is invoked
                    taskAwaiter = this.<httpClient>5__1.GetStringAsync(this.url).GetAwaiter();
                    
                    //[performance] check if this task has completed already,
                    //if it did, skip scheduling and boxing
                    if (!taskAwaiter.get_IsCompleted())
                    {
                        this.<>1__state = 0;
                        this.<>u__$awaiter3 = taskAwaiter;
                        
                        //Schedules the state machine to proceed
                        //to the next action when the specified awaiter completes.
                        //Also: sending this state machine here will trigger it's boxing into heap.
                        this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, 
                          AsyncMethods.<DownloadHtmlAsyncTask>d__0>(ref taskAwaiter, ref this);
                        
                        //release cpu knowing our next step will be called by Framework.
                        return;
                    }
                }
                else
                {
                    //set awaiter to null
                    taskAwaiter = this.<>u__$awaiter3;
                    this.<>u__$awaiter3 = default(TaskAwaiter<string>);
                    
                    //set state to initial state (temporarily)
                    this.<>1__state = -1;
                }
                
                //second (+ final) state code (state=0): set result to member, printout
                string arg_A5_0 = taskAwaiter.GetResult();
                
                //set awaiter to null
                taskAwaiter = default(TaskAwaiter<string>);
                
                //set StateMachine's result field, and end code (print out)
                string text = arg_A5_0;
                this.<result>5__2 = text;
                Debug.WriteLine("after await");
                
                //set return task's result
                result = this.<result>5__2;
            }
        }
        //exception handling is done here
        catch (Exception exception)
        {
            //set machine state to final state
            this.<>1__state = -2;
            this.<>t__builder.SetException(exception);
            return;
        }
        this.<>1__state = -2;
        this.<>t__builder.SetResult(result);
    }
    [DebuggerHidden]
    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)
    {
        this.<>t__builder.SetStateMachine(param0);
    }
}

 

Several performance tricks have been added here:

  • The State machine is created as a struct, which means it's on stack
  • It is moved to heap inside the AwaitUnsafeOnCompleted function (deeper into the mechanism it is cast to IAsyncStateMachine which triggers the boxing).
  • This call may be skipped if the task we await ends before the taskAwaiter.IsComplete is checked.

This function has only two code fragments (two states) as there is only one “await” dividing the code. This may be a standard scenario but not an especially interesting one.

Drawing the state machine above as an automaton, we will give us the following diagram:

 

The 1st state machine drawn as a deterministic automaton:

machine1.PNG

 

What happens when the StateMachine has more states?

This brings us to the second example... Let’s turn to the second function to see.

 

Decoding the second function:

DownloadHtmlAsyncTask - using two await calls:

The calling code:

 

Calling code 2nd function:

[DebuggerStepThrough, AsyncStateMachine(typeof(AsyncMethods.<DownloadWithUrlTrackingTaskAsync>d__5))]
private Task<string> DownloadWithUrlTrackingTaskAsync(string url)
{
    AsyncMethods.<DownloadWithUrlTrackingTaskAsync>d__5 <DownloadWithUrlTrackingTaskAsync>d__;
    <DownloadWithUrlTrackingTaskAsync>d__.<>4__this = this;
    <DownloadWithUrlTrackingTaskAsync>d__.url = url;
    <DownloadWithUrlTrackingTaskAsync>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
    
    //set initial machine state to -1
    <DownloadWithUrlTrackingTaskAsync>d__.<>1__state = -1;
    AsyncTaskMethodBuilder<string> <>t__builder = 
      <DownloadWithUrlTrackingTaskAsync>d__.<>t__builder;
    <>t__builder.Start<AsyncMethods.<DownloadWithUrlTrackingTaskAsync>d__5>(ref <DownloadWithUrlTrackingTaskAsync>d__);
    return <DownloadWithUrlTrackingTaskAsync>d__.<>t__builder.get_Task();
}

 

The state machine definition: (with my comments inline)

 

State machine code 2nd function:

using ConsoleApplication1;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[CompilerGenerated]
[StructLayout(LayoutKind.Auto)]
private struct <DownloadWithUrlTrackingTaskAsync>d__5 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder<string> <>t__builder;
    public AsyncMethods <>4__this;
    public string url;
    public string <Data>5__6;
    public string <Data1>5__7;
    private TaskAwaiter<string> <>u__$awaiter8;
    private object <>t__stack;
    void IAsyncStateMachine.MoveNext()
    {
        string result;
        try
        {
            TaskAwaiter<string> taskAwaiter;
            //initialy state = -1, so we skip this at start
            switch (this.<>1__state)
            {
            case -3:
                //request machine to stop!
                //machine will go to end state and set result if one exists.
                goto IL_168;
            case 0:
                taskAwaiter = this.<>u__$awaiter8;
                this.<>u__$awaiter8 = default(TaskAwaiter<string>);
                //set state to initial state (temporarily)
                this.<>1__state = -1;
                goto IL_A1;
            case 1:
                taskAwaiter = this.<>u__$awaiter8;
                this.<>u__$awaiter8 = default(TaskAwaiter<string>);
                //set state to initial state (temporarily)
                this.<>1__state = -1;
                goto IL_121;
            }
            
            // first state code state=-1, 
            //printout, and await, then return control to scheduler
            Debug.WriteLine("before await1");
            taskAwaiter = AsyncMethods.DownloadHtmlAsyncTask(this.url).GetAwaiter();
            if (!taskAwaiter.get_IsCompleted())
            {
                //set state to next step (0)
                this.<>1__state = 0;
                this.<>u__$awaiter8 = taskAwaiter;
                this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, 
                  AsyncMethods.<DownloadWithUrlTrackingTaskAsync>d__5>(ref taskAwaiter, ref this);
                return;
            }
            IL_A1:
            //second state code (state = 0), set the result of the first call to member, 
            //printout, schedule next await, yield control
            string arg_B0_0 = taskAwaiter.GetResult();
            taskAwaiter = default(TaskAwaiter<string>);
            string text = arg_B0_0;
            this.<Data>5__6 = text;
            Debug.WriteLine("before await2");
            taskAwaiter = AsyncMethods.DownloadHtmlAsyncTask(this.url).GetAwaiter();
            if (!taskAwaiter.get_IsCompleted())
            {
                //set state to next step (1)
                this.<>1__state = 1;
                this.<>u__$awaiter8 = taskAwaiter;
                this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, 
                  AsyncMethods.<DownloadWithUrlTrackingTaskAsync>d__5>(ref taskAwaiter, ref this);
                return;
            }
            IL_121:
            //third state code (state = 1), 
            //set the result of the first call to member, printout, 
            //set the (function's) return task's result.
            string arg_130_0 = taskAwaiter.GetResult();
            taskAwaiter = default(TaskAwaiter<string>);
            text = arg_130_0;
            this.<Data1>5__7 = text;
            Debug.WriteLine("the end.");
            result = this.<Data>5__6;
        }
        catch (Exception exception)
        {
            //some exception handling: set end state (-2)
            this.<>1__state = -2;
            //set the exception in the builder
            this.<>t__builder.SetException(exception);
            return;
        }
        IL_168:
        //if no exception set end state and result
        this.<>1__state = -2;
        this.<>t__builder.SetResult(result);
    }
    [DebuggerHidden]
    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)
    {
        this.<>t__builder.SetStateMachine(param0);
    }
}

 

Though the code for multiple states (more than two) is written differently, the diagram of this state machine looks very similar:

 

The 2nd state machine diagram:

 macnine2.PNG

 

Points of Interest

There are few points to be mentioned:

  • The performance overhead of this framework:
    • Anyone can see that there are "several" extra code lines added.
    • However, according to Microsoft:
      • About 40 operations are used to create async state machine (microseconds).
      • This overhead is equivalent to or less than other async mechanisms (like TPL).
      • This can be a problem only when calling await many times in a tight loop.
    • In my opinion, The overhead is negligible compared to the benefit of freeing up the GUI thread (thus avoiding frozen user interfaces), while the code flow remains readable and unbroken.
  • Generated exception handling:
    • In the generated code we can see that all of the exceptions are caught.
    • The exceptions are stored in the builder and re-thrown by it at the end, producing the behavior we saw in part 1 of this article.
    • The builder also holds the results of the awaited task for later use.

Conclusion

 In this part we looked behind the scenes of the Async/Await mechanism. I hope this dispels some of the mystery around this API and paves the way for you to use it correctly with confidence.In this post we learned:

  • A state machine is generated for us when a function is marked as “async”
  • The inner structure and workings of the state machine
  • The state machine though not small in size, was optimized to minimize its performance penalty
    • Will remain as a struct in memory until moving to heap is required
    • Optimization for cases when call returns before await is needed
    • Optimization for cases with 2 fragments (different code)
    • Exceptions during the code are caught and held inside the builder object

I believe that in order to produce correct code, you need to know your framework inside out so you are ready for all its behaviors (not only the common ones). Therefore, when you do encounter strange behaviors, you know how to interpret them.

Leave a Comment

We encourage you to share your comments on this post. Comments are moderated and will be reviewed
and posted as promptly as possible during regular business hours

To ensure your comment is published, be sure to follow the Community Guidelines.

Be sure to enter a unique name. You can't reuse a name that's already in use.
Be sure to enter a unique email address. You can't reuse an email address that's already in use.
Type the characters you see in the picture above.Type the words you hear.
Search
Showing results for 
Search instead for 
Do you mean 
About the Author
I've been all over the coding world since earning my degrees have worked several years in c++ and then several in java, finally setteling i...


Follow Us
The opinions expressed above are the personal opinions of the authors, not of HP. By using this site, you accept the Terms of Use and Rules of Participation