Async 3 - Understanding How Async State Machine Works

Exploring under the hood details of async await state machine

Fri, 15 Feb 2019

Intro

This post is part of 4 post blog series. In this series i will start with an introduction about the asynchronous programming and then we will explore more details about the asynchronous programming and it benefits then finally we will go through how we can start using asynchronous programming in our applications. This is the Third Post in the series.

Below is the list of all the posts from the series

  1. What is Asynchronous execution Real life and C# example
  2. Why you should you consider it as a web developer?
  3. How async/await works Details of StateMachine working(This Post)
  4. Demo: Refactoring an application to be asynchronous

How async/await works, State Machine explained

This article is optional as it explains the under the hood details of async/await implementation this information is not required to write asynchronous code but help with understanding async/await and its functionality.

Story so far from last two posts. In first post we discussed about what actually asynchronous programming is about and how we can use async/await to write asynchronous code. Then in second article we tried to understand the need of asynchronous programming in web applications. We looked at basic code samples also. We will have real world example of Asynchronous programming after this blog article.

Now we have basic understanding of async/await and we are convinced that it is important to our applications and we interested in finding out how it works under the hood.

In very simple terms the async/await is a sort of syntax sugar. Each async method will be translated into a StateMachine and then calling method use this StateMachine to execute business logic.

Few people like to have theory first few people like to see code straightaway. I am planning to use a hybrid approach Where we will have a small dose of theory and then all code for State Machine (With some helpful comments) then we will try to draw a picture to explain the code execution flow inside a state machine.

Few Terms that is used throughout the post.

  • WorkerFunction: Method will be the actual asynchronous work need to be done.
  • CallingFunction: Method that will call the WorkerFunction.
  • FirstCall: First time the MoveNext of StateMachine is called (Synchronous Flow)
  • WakeUpCall: After the await results are available and code continue from where it left.Sort of callback.

If you cannot make any sense of these terms don’t worry they will be lot more understandable as we progress in the post.

What happen when we compile the code (Brief - Theory)

We take our code snippet and paste it into http://Sharplab.io and then it will generate a compiled code for the code snippet. These are the few things that compiler will generate for our asynchronous code.

  1. Compiler will generate a StateMachine (IAsyncStateMachine) code for the WorkerFunction.
  2. Move the actual Logic of WorkerFunction to MoveNext function of state machine.
  3. Create variable inside StateMachine to maintain variable needed for StateMachine operation.
  4. CallingFunction is changed to create a new instance of StateMachine
  5. Call Start on one of the StateMachine TaskMethodGenerator (More details below) in CallingFunction

Time to see some code

Here is a very simple piece of code which make use of async/await keyword. I am keeping the code sample complexity to bare minimum as our focus is on understanding the working of async/await not the possible applications of async/await. That deserves a blog post on it’s own.

All the comments starting with FirstCall are executed on the first call or synchronous flow and all the comments starting with WakeupCall will be executed on the continuation of the task(Callback)

using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;

namespace Scenario
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                AsyncDownload().GetAwaiter().GetResult();
                Console.ReadLine();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

        static async Task<string> AsyncDownload()
        {
            HttpClient client = new HttpClient();
            //Asynchronously download the contents of a web page
            return await client.GetStringAsync("https://msdn.microsoft.com");
        }

    }
}

I pasted this code to http://sharplab.io and compiled the code in the Debug and the output generated was something like below.

I added few comments to the code generated to help with Understanding the flow of execution.

using System;
using System.Diagnostics;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
namespace Scenario
{
    internal class Program
    {
        //State machine generated to represent the async method to download
        //Using http client
        [CompilerGenerated]
        private sealed class <AsyncDownload>d__1 : IAsyncStateMachine
        {
            //Variable to maintain current execution state of the state machine
            //FirstCall: Initial value -1
            public int <>1__state;

            //Task builder to create a new task to execute this StateMachine code
            public AsyncTaskMethodBuilder<string> <>t__builder;

            //Http client used by the method to download content of remote url
            private HttpClient <client>5__1;

            //Variable to store the results of the HttpClient call 
            private string <>s__2;

            //Awaiter for the task to download contents using Http client
            private TaskAwaiter<string> <>u__1;

            //This is the section where actual logic of original method exist
            //Contain the logic to execute the code till await statement 
            //Also configure stuff of the wake up call when async method complete it's execution
            private void MoveNext()
            {
                //Copy current state to local variable 
                //The initial value for state will be -1
                int num = <>1__state;
                //Variable to save the result of Http call
                string result;
                try
                {
                    //Variable to save awaiter for the new task 
                    TaskAwaiter<string> awaiter;
                    //On first time the num will be -1
                    if (num != 0)
                    {
                        //FirstCall: we will get here on first call
                        <client>5__1 = new HttpClient();
                        //FirstCall: Save awaiter for http call into a variable
                        awaiter = <client>5__1.GetStringAsync("https://msdn.microsoft.com").GetAwaiter();
                        //FirstCall: Most probably we will go into this block on the first call
                        //FirstCall: This block is for optimization in case the task is already finished
                        //FirstCall: We skip scheduling the wakeup call or continuation
                        if (!awaiter.IsCompleted)
                        {
                            //FirstCall: We set the state variable to 0 
                            //FirstCall: So that on wakeup or callback we do not enter this block at all.
                            num = (<>1__state = 0);
                            <>u__1 = awaiter;
                            <AsyncDownload>d__1 stateMachine = this;
                            //FirstCall: This call to AwaitUnsafeOnCompleted is where most of the magic happens
                            //FirstCall:In this step we register the StateMachine as continuation of the task by calling AwaitUnsafeOnCompleted 
                            //But how it is done? 
                            //builder.AwaitUnsafeOnCompleted do multiple things in background
                            //FirstCall: 1. TaskMethodBuilder captures Execution context 
                            //FirstCall: 2. Create an MoveNextAction using Execution context 
                            //FirstCall: 3. This MoveNextAction will call the MoveNext of state machine and provide execution context
                            //FirstCall: 4. Set MoveNextAction as callback to awaiter on complete Using awaiter.UnsafeOnCompleted(action)
                            <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                            //FirstCall: Release cpu or thread to the caller 
                            //FirstCall: Waiting game begins
                            return;
                        }
                    }
                    else
                    {
                        //WakeupCall: We get awaiter from the execution context
                        awaiter = <>u__1;
                        //WakeupCall: Set awaiter to null to release memory
                        <>u__1 = default(TaskAwaiter<string>);
                        //WakeupCall: Set State variable to -1(initialState) temporarily
                        num = (<>1__state = -1);
                    }
                    //WakeupCall: Get result from the awaiter 
                    //WakeupCall:The awaiter must be completed as we received the WakeUpCall
                    //WakeUpCall: Get Result from the awaiter
                    <>s__2 = awaiter.GetResult();
                    result = <>s__2;
                }
                catch (Exception exception)
                {
                    <>1__state = -2;
                    <>t__builder.SetException(exception);
                    return;
                }
                //WakeUpCall: Set the state to final state we are done
                <>1__state = -2;
                //WakeUpCall: Set the result on the task builder
                <>t__builder.SetResult(result);
            }

            void IAsyncStateMachine.MoveNext()
            {
                //ILSpy generated this explicit interface implementation from .override directive in MoveNext
                this.MoveNext();
            }

            [DebuggerHidden]
            private void SetStateMachine(IAsyncStateMachine stateMachine)
            {
            }

            void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
            {
                //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
                this.SetStateMachine(stateMachine);
            }
        }
        
        //We will call the Main function as CallingFunction
        private static void Main(string[] args)
        {
            try
            {
                AsyncDownload().GetAwaiter().GetResult();
                Console.ReadLine();
            }
            catch (Exception value)
            {
                Console.WriteLine(value);
                throw;
            }
        }

        //We will call this method WorkerMethod
        [AsyncStateMachine(typeof(<AsyncDownload>d__1))]
        [DebuggerStepThrough]
        private static Task<string> AsyncDownload()
        {
            //FirstCall: Create new Instance of StateMachine
            <AsyncDownload>d__1 stateMachine = new <AsyncDownload>d__1();
            //FirstCall: Create new Instance of AsyncTaskMethodBuilder and set it up for state machine
            stateMachine.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
            //FirstCall: Set StateMachine state to -1 (Initial)
            stateMachine.<>1__state = -1;
            AsyncTaskMethodBuilder<string> <>t__builder = stateMachine.<>t__builder;
            //FirstCall: Call Start on Builder that will result in call to StateMachine.MoveNext();
            <>t__builder.Start(ref stateMachine);
            //FirstCall: Return the task from Builder from the WorkerMethod
            return stateMachine.<>t__builder.Task;
        }
    }
}

Explanation of the Code above

If you have already read through the code sample above and still things don’t make complete sense.

Let me draw a picture this time. This may help with understanding of the code execution flow.

I tried to use some color pattern please let me know in comments if that helped in understanding the flow of execution.

  • All the boxes with Red Border will be executed on the both FirstCall and WakeUpCall.
  • Blue Boxes will be executed on FirstCall only
  • Green Boxes may Get Executed on FirstCall if awaiter is completed already but highly unlikely this flow is there of optimizations.
  • Green Boxes will be executed on WakeUpCall for sure in case of there are no errors or exception

For this blog post i am considering happy paths. Please let me know if you are interested in reading more about the exception scenarios also.

Scenario

See you soon

I will be back soon with a post about How we can refactor an application from synchronous to asynchronous. We will refactor the repository layer first. Then we will update the controllers consuming these repositories. Finally we will udpate our unit tests for asynchronous controller actions.

Loading...
Ranjeet Singh

Ranjeet Singh Full Stack Developer (.NET, ReactJS, Redux, Azure) You can find me on twitter @NotRanjeet or on LinkedIn at LinkedIn