Exploring Async Await and State Machine in C#

Exploring Async Await and State Machine in C#

Introduction

This post is based on the research of with reference to an article by Vasil Kosturski originally presented at https://shorturl.at/dFfll

An application involves various types of operations which are performed in some order. Some of these operations are not CPU-bound but depend on the responsiveness of external peripherals, storage media, or network services. These operations can block the primary execution thread while they wait for the operation to complete, and negatively impact the responsiveness of the application as a result.?

Async methods are non-blocking operations that continue the remaining portion of the method as a continuation to the awaited task and return control to the calling method. Asynchronous methods do not require their dedicated thread. Rather, an asynchronous method runs on the current synchronisation context by default.

The async operations are performed by the underlying system, there is no thread involved in the execution of a pure asynchronous operation. The execution thread calls the underlying system utilities for performing the asynchronous operation and then returns to the thread pool. The underlying system operates and meanwhile returns an uncompleted task. This leads to the suspension of the async method and the application continues its execution. The interesting point is that in the current phase, the asynchronous operation is not being performed by any thread. The underlying system utilities are responsible for performing the asynchronous operation. Once the system utilities have completed the asynchronous operation, they notify the application of the completion of the task. The suspended async method’s execution is then resumed.

Evaluation of an await expression

During the execution of an async method, the compiler runs the code synchronously until it finds an asynchronous call is made. If no asynchronous call is made the method gets executed synchronously.?

The “await” keyword can be used before any “awaitable” instance. Awaitable instance has an instance method named “GetAwaiter” which returns “Awaiter”. There are various built-in “awaiter” provided in C#, but we can implement our custom awaiter.

Awaiter

There are some rules which have to be followed by all Awaiters. The rules are mentioned below :?

  1. The awaiter should implement the System.Runtime.INotifyCompletion interface. The interface contains a single method named void OnCompleted(Action) this method should also be implemented, this method is called when the awaiting task’s execution is completed.
  2. The awaiter should have a readable instance property named bool IsCompleted. This property will indicate the completion of the instance method.
  3. The awaiter should have an instance method named GetResult. This method can return data or it can be void. The return type of the method determines the type of the result of the async operation.

Each of these rules makes sure that the awaiter implements the three important components of the awaiter and helps at the time of State Machine execution, we will discuss the State Machine in the later part of this blog.

As discussed above whenever the await expression is found inside an async method, the compiler starts the execution of the awaiter operation and checks for its completion using the IsCompleted property. If the IsCompleted property is true then, the OnCompleted method call is made and the GetResult method call is made, to get the result of the asynchronous operation. Sometimes the result may be void whereas sometimes it can be some concrete value, hence the return type of the GetResult method will determine the type of result of the async operation.

A brief evaluation of the await expression can be examined from the diagram presented below.

The runtime machine finds an await operation and executes the code synchronously until an asynchronous call is made or the operation is completed. After this, the awaiter is obtained using the call made to the GetAwaiter method of the awaitable. Once the awaiter is obtained the IsCompleted property of the awaiter instance is checked. If the property value is true then the GetResult method call is made. If the value of IsCompleted is false then the OnCompleted method is called which takes a continuation action as a parameter. This continuation is invoked as soon as the operation is executed.

State Machine

At the time of compilation of the source code, the compiler converts the async method to a state machine. The state machine comprises different states of the execution of the asynchronous method. The state of the async method changes after each awaits expression. These states decide the execution of the async method. The await keyword marks as a very special checkpoint in an async method. The occurrence of await expression may pause the execution of the method and return an incomplete task. These pauses may define the states of the method of execution. This state machine is a nested struct which is constructed by the compiler.

This struct has a MoveNext method which is executed whenever synchronously or asynchronously state code is executed. The parameters of the asynchronous method are stored as instance fields of this state machine struct. These instance fields are used to preserve the context across continuation. The current state of the state machine is represented by a private instance field named state. ?Let’s try to understand the state machine with an example.


public async Task LogAsync()
{
    Console.WriteLine("Main call");
    Console.WriteLine("Before 1st await : ");
    var firstAwait = await new CustomAwaitable("Pratik");
    var secondAwait = await new CustomAwaitable("Ayush");
    Console.WriteLine($"{firstAwait} {secondAwait}");
}
        

The above mentioned code is an example of an Asynchronous program which makes an await call to the CustomAwaitable. The CustomAwaitable returns the string provided as argument to the constructor.? Let us write the code for the custom state machine for the asynchronous method mentioned above.

public class CustomStateMachine
{
    private int _state;

    public readonly TaskCompletionSource _taskBuilder = new TaskCompletionSource();

    private CustomAwaiter<string> _awaiter;

    private string _firstAwait;
    private string _secondAwait;

    public Task ResultTask => _taskBuilder.Task;

    public CustomStateMachine(int initialState)
    {
        _state = initialState;
    }

    public void MoveNext()
    {
        try
        {
            if (_state == 0)
            {
                Console.WriteLine("Main call" );
                Console.WriteLine("Before 1st await :");
                _state = 1;
                _awaiter = new CustomAwaitable("Pratik").GetAwaiter();
                if (!_awaiter.IsCompleted)
                {
                    _awaiter.OnCompleted(MoveNext);
                    return;
                }
            }
            if (_state == 1)
            {
                _firstAwait = _awaiter.GetResult();
                Console.WriteLine("Before 2nd await");
                _state = 2;
                _awaiter = new CustomAwaitable("Ayush").GetAwaiter();
                if (!_awaiter.IsCompleted)
                {
                    _awaiter.OnCompleted(MoveNext);
                    return;
                }
            }
            if (_state == 2)
            {
                _secondAwait = _awaiter.GetResult();
                Console.WriteLine($"{_firstAwait} {_secondAwait}");
                _taskBuilder.SetResult();
                return;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            _taskBuilder.SetException(ex);
        }
    }
}
        

The above code mentions the class of the state machine ( Note that the compiler generates a struct for the state machine ). The state machine contains the private instance fields with the name firstAwait and secondAwait. These fields are of type “string”. There is another private instance field named awaiter of type “CustomAwaiter<string>” along with it there is also a field of type TaskCompletionSource with name taskBuilder. The state of the state machine is represented by the private instance with the name state which is of type “int”. The state of the state machine is initialised in the constructor of the CustomStateMachine class. The CustomStateMachine class also contains the MoveNext method which executes different code based on the state of the async method.

Dissecting the MoveNext method of the CustomStateMachine class

The MoveNext method executes the code till the initial state of the state machine. It updates the state of the state machine to the next state and sets the value of the awaiter field with the awaiter of the CustomAwaiter class with “Pratik” as the initial value for the data field of the CustomAwaitable class. After the value for the awiter field is set then the IsCompleted property of the instance is checked. If the value of IsCompleted is false then the MoveNext method is provided as an argument to the OnCompleted method of the _awaiter field. This sets the MoveNext method to be executed as soon as the first await operation is completed.?

Similarly, the MoveNext method is executed till the time all the state codes are executed completely once all the state codes are executed, the last Console.WriteLine method is called. This line writes a string with the value of the first await operation as well as that of the second await operation. In this fashion, all the asynchronous operations are performed using the “State Machine”.

Conclusion

The ability to perform long-running tasks simultaneously on another thread so that the main execution thread is not blocked is provided by asynchronous programming. A pure asynchronous operation is not performed by any thread, the underlying system is responsible for performing it. Asynchronous programming can be implemented using various ways such as the callback approach and some other approaches. Async and await keywords are used to implement asynchronous methods in a less verbose way. The async method is converted to a state machine struct by the compiler during the compile time. The state machine contains code of the async method with block separations based on the state of execution. The await keyword is a special keyword for marking as a checkpoint for state transition. The state machine struct has a method named MoveNext as well as a private instance field named state. The MoveNext method is called every time an asynchronous operation is completed. The state of the state machine is changed every time before performing the asynchronous operation. Based on the value of the state different code sections of the async method are executed and the final result is obtained.

Authored by : Pratik Singh Thakur .

Viktor Sorokovikov

Senior Full Stack Developer at Coherent Solutions

1 个月

BTW in the original code the line is missed: > Console.WriteLine("Before 2nd await")

Vasil Kosturski

Tech Lead | Distributed Systems | Blogger

3 个月

Dear Author, I noticed that your article closely resembles a series I authored on the async/await state machine in C# at my blog. For examlpe: https://vkontech.com/exploring-the-async-await-state-machine-the-awaitable-pattern/ https://vkontech.com/exploring-the-async-await-state-machine-main-workflow-and-state-transitions/ https://vkontech.com/exploring-the-async-await-state-machine-conceptual-implementation/ While I'm glad this topic interests you, I believe it's important to credit the original work. Could you please provide proper attribution or consider revising the content? I’m happy to discuss this further. Best regards, Vasil Kosturski

回复
Vanasis Baboomi

Experienced ASP.NET Full Stack Developer with Advanced Database and UI Skills Ready for Exciting Opportunities

7 个月

Great Article

Darshan Chaudhari

Software Engineer at Sarvaha Systems & CF specialist

7 个月

Great ??

Pratik Ghodke

Software Engineer at Sarvaha Systems

7 个月

Good Information

要查看或添加评论,请登录

社区洞察

其他会员也浏览了