Basic implementations of async/await and related performance improvements in. NET Core 2.1

Source: Internet
Author: User

Objective

At the beginning of this article, I would like to say two more sentences, but also for the future to never talk about such words.

In the daily work, the most contact development work is still on the. NET Core platform, of course, because of the openness of team leadership and the diversity of team style (which is inseparable from the CTO and the supervisor's personal ability), the industry's cutting-edge technology concepts can also be seen in the start-up projects. So although the team is still in the rapid development, there are some strange things, the work content is also tight and loose, but the overall is also a bitter and happy, not very exclusive.

In fact, this kind of environment is similar to the author's heart of the "Holy Land" Thoughtworks embryonic (TW's HR come to me, AH), the author and girlfriend talked about their most want to do the job is also technical advice. The development concept of such a technical consulting company can be summed up in one sentence: following extensible development, rapid iteration, sustainable deployment, architecture design, and the pursuit of the most superior team-based technology selection decision in the target application scenario .

So the struggle for language, the Battle of the platform, fall to every developer interested in programming and problem solving, it becomes the most insignificant problem. The ability to feel the impact of different technologies, to appreciate the subtleties of different architectural ideas, is already a matter of satisfaction, and until the team needs you to quickly apply other technology selection, the previous effort is also helping. Of course, the wage-oriented programming is also a trade-off, I think the time will be caught in this cycle, so hope in the continuous learning and practice, can let themselves more satisfied with it.

The famous DRY principle tells us--don ' t repeat yourself, and the author wants to go further, deep Dive and Wide mind, dig deeper and try more.

The strange preface is over.

As the latest official version, although the version number is only a minor upgrade, the. NET Core 2.1 has a significant performance boost compared to. NET Core 2.0. Whether it's project build speed, string manipulation, network transport, and JIT inline method performance, you can say that today's. NET Core has proactively brought developers to the savings experience of keying to bytes. See also performance improvements in. NET Core 2.1 for a specific introduction.

In this article, I want to talk about some of the underlying principles of async/await and. NET Core 2.1 Optimizations on the assignment of asynchronous operations objects.

Introduction to ASYNC/AWAIT implementation

Developers familiar with asynchronous operations know that the async/await implementation is basically a skeleton code (Template method) and a state machine.

From the anti-compiler we can get a glimpse of the skeleton approach. Suppose there is such a sample program

internal class Program{    private static void Main()    {        var result = AsyncMethods.CallMethodAsync("async/await").GetAwaiter().GetResult();        Console.WriteLine(result);    }}internal static class AsyncMethods{    internal static async Task<int> CallMethodAsync(string arg)    {        var result = await MethodAsync(arg);        await Task.Delay(result);        return result;    }    private static async Task<int> MethodAsync(string arg)    {        var total = arg.First() + arg.Last();        await Task.Delay(total);        return total;    }}

In order to better display the compiled code, the asynchronous operation is specifically divided into two methods to achieve, that is, the formation of an asynchronous operation chain. This "intrusive" pass is actually friendlier to development, and when part of the code uses asynchronous code, the entire chain of delivery has to be asynchronous in the right way. Let's take a look at the skeleton methods and state machines generated by the compiler for the above async methods (and have been beautified to produce readable C # code).

[DebuggerStepThrough] [Asyncstatemachine ((typeof (Callmethodasyncstatemachine)]private static task<int> Callmethodasync (String arg) {Callmethodasyncstatemachine StateMachine = new Callmethodasyncstatemachine {arg = arg, builder = Async Taskmethodbuilder<int>.    Create (), state =-1}; Statemachine.builder.start<callmethodasyncstatemachine> (ref StateMachine) = {//Skeleton method starts the first time Movenex    T method Statemachine.movenext ();        }); return stateMachine.builder.Task;} [DebuggerStepThrough]    [Asyncstatemachine ((typeof (Methodasyncstatemachine)]private static task<int> MethodAsync (string arg) { Methodasyncstatemachine StateMachine = new Methodasyncstatemachine {arg = arg, builder = Asynctaskmethodbui Lder<int>.        Create (), state =-1}; Restore delegate function Action __movenext = () = {statemachine.builder.start<callmethodasyncstatemachine> (ref s    Tatemachine); } __movenext (); return stateMachine.builder.Task;}
    • Methodasync/callmethodasync-Skeleton method
    • Methodasyncstatemachine/callmethodasyncstatemachine-Asynchronous operations for each async tag produce a skeleton method and a state machine object
    • Arg-How many parameters are obviously on the original code, and how many fields are in the generated code
    • __movenext-Restore delegate function, corresponding to the MoveNext method in the state machine, which returns the awaiter of the corresponding task as a callback function during execution so that MoveNext continues to execute
    • Builder-This structure is responsible for connecting the state machine and the skeleton method
    • State-always starting from 1, when the method executes is 1, the non-negative value represents the target of a subsequent action, and the state at the end is-2
    • Task-Represents the tasks propagated after the current asynchronous operation completes with the correct results

As you can see, each asynchronous operation that is marked by the Async keyword produces a corresponding skeleton method, and the state machine is created and run in the skeleton method. The following is an example of the actual state machine internal code, which allows us to actually do a callmethodasyncstatemachine with two-step asynchronous operations.

[Compilergenerated]private sealed class callmethodasyncstatemachine:iasyncstatemachine{public int state;  public string arg;        Represents the variable public asynctaskmethodbuilder<int> builder;         Represents result private int result;    Represents var result = await MethodAsync (ARG);          Private task<int> firsttasktoawait;    Represents await task.delay (result);     Private Task secondtasktoawait;  private void MoveNext () {try {switch (this.state)//Initial value is-1 {case                    -1://execute await MethodAsync (ARG);                                        this.firsttasktoawait = Asyncmethods.methodasync (This.arg);                    When firsttasktoawait executed this.result = Firsttasktoawait.result;                                        this.state = 0; Call this.                    MoveNext (); This.                Builder.awaitunsafeoncompleted (ref this.awaiter, ref this); Case 0://execution Task.delay (result) this.secondtasktoawait = Task.delay (This.result);                                        When secondtasktoawait executes this.state = 1; Call this.                    MoveNext ();                This.builder.AwaitUnsafeOnCompleted (ref this.awaiter, ref this);                    Case 1:this.builder.setresult (Result);            Return            }} catch (Exception Exception) {this.state =-2;            This.builder.SetException (Exception);        Return }} [Debuggerhidden] private void Setstatemachine (Iasyncstatemachine statemachine) {}}

You can see that there are several asynchronous methods in an async method, and there are several branch judgments in the state machine. Depending on the execution of each branch, the MoveNext method is called to ensure that all async methods are fully executed. Further, it seems that the branching method of switch and case is still essentially a chain of the execution and delivery of an asynchronous operation.

The Callmethodasync method described above can also be converted into the following task.continuewith forms:

internal static async Task<int> CallMethodAsync(string arg){    var result = await (                    await MethodAsync(arg).ContinueWith(async MethodAsyncTask =>                        {                            var methodAsyncTaskResult = await MethodAsyncTask;                            Console.Write(methodAsyncTaskResult);                            await Task.Delay(methodAsyncTaskResult);                            return methodAsyncTaskResult;                        }));    return result;}

It can be understood that, in general, each time the compiler encounters an await, the currently executing method registers the remainder of the method as a callback function (the work to be done after the current await task completes, or it may contain an await task, which can still be nested sequentially), and then return immediately (return Builder. Task). Each remaining task will somehow complete its operation (which may be scheduled to run as an event on the current thread, or because it is performed using an I/O thread, or continues on a separate thread), and it is not important to continue with the next await until the previous await task mark is complete Task. For a fantastic idea, see pause and play through Await

. NET Core 2.1 Performance improvements

The previous section on compiler-generated content does not fully cover all of async/await's implementation concepts, even a small subset of them, such as the author does not mention the contents of the wait mode (iawaitable) and execution context (ExecutionContext), the former Async/await implements the guidelines, the latter is the actual execution of asynchronous code, returned to the caller results and thread synchronization of the manipulators. Included in the generated state machine code, when the first execution discovery task is not completed (!awaiter.iscompleted), the task is returned directly.

The main reason is that these content is afraid to spend a lot of space, interested students recommend to see "in-depth understanding of C #" and ExecutionContext.

Asynchronous code can significantly improve server responsiveness and throughput performance. But through the above explanation, we must have realized that in order to achieve asynchronous operation, the compiler will automatically generate a large number of skeleton methods and state machine code, the application usually also to allocate more related operations objects, thread scheduling synchronization is also time consuming force, This also means that asynchronous operations are often less likely to run than synchronous code (which is not contradictory to the performance gains of the first sentence, and that people who weigh more may be slower, but have a stronger ability to fight).

But the framework developers have been working on the authors of this promotion, and this is also mentioned in the latest. NET Core 2.1 release. In the original application, a task based on the async/await operation assigns the following four objects:

    1. Task returned to the caller
      When the task is actually completed, the caller can know information such as the return value of the task
    2. State machine information that is boxed onto the heap
      In the previous code, we used the ref identity at the beginning, the state machine is actually stored on the stack in the form of a structure, but inevitably, when the state runs, it needs to be boxed onto the heap to keep some running state
    3. Delegate passed to Awaiter
      That is _moveNext , when a Task in the chain is completed, the delegate is passed to the next Awaiter execution MoveNext method.
    4. State machine Performer (Movenextrunner) that stores information about certain contexts (such as ExecutionContext)

According to the performance improvements in. NET Core 2.1 article:

for (int i = 0; i < 1000; i++){    await Yield();    async Task Yield() => await Task.Yield();}

The current app will allocate the objects in:

In. NET Core 2.1, the final allocation object will be only:

The four allocated objects are eventually reduced to one, and the allocation space is reduced to half the past. For more information on implementation, refer to Avoid Async method delegate allocation.

Conclusion

This article focuses on the implementation of async/await and related content in. NET Core 2.1 On performance optimizations for asynchronous operations. Because the author level general, the article space is limited, cannot perfect explanation complete, also hoped that everybody forgive.

Regardless of the platform, the asynchronous operation is an important part, and I think any developer in the use, should be more appropriate to understand the story behind. In the specific development, C # borrowed from the asynchronous implementation of F #, other languages such as JS may also draw on some of the content of C #, of course, some basic terms, such as callbacks or feature, anywhere is similar, how can not be separated from the computer system, which also shows the importance of the programming foundation.

Reference documents
    1. Pause and play through Await
    2. Asynchronous programming is easier with the new Visual Studio async CTP

Basic implementations of async/await and related performance improvements in. NET Core 2.1

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.