async/await 的基本实现和 .NET Core 2.1 中相关性能提升

前言

这篇文章的开头,笔者想多说两句,不过也是为了以后再也不多嘴这样的话。

在日常工作中,笔者接触得最多的开发工作仍然是在 .NET Core 平台上,当然因为团队领导的开放性和团队风格的多样性(这和 CTO 以及主管的个人能力也是分不开的),业界前沿的技术概念也都能在上手的项目中出现。所以虽然现在团队仍然处于疾速的发展中,也存在一些奇奇怪怪的事情,工作内容也算有紧有松,但是总体来说也算有苦有乐,不是十分排斥。

其实这样的环境有些类似于笔者心中的“圣地” Thoughtworks 的 雏形(TW的HR快来找我啊),笔者和女朋友谈到自己最想做的工作也是技术咨询。此类技术咨询公司的开发理念基本可以用一句概括:遵循可扩展开发,可快速迭代,可持续部署,可的架构设计,追求目标应用场景下最优于团队的技术选型决策

所以语言之争也好,平台之争也好,落到每一个对编程和解决问题感兴趣的开发者身上,便成了最微不足道的问题。能够感受不同技术间的碰撞,领略到不同架构思想中的精妙,就已经是一件满足的事情了,等到团队需要你快速应用其他技术选型时,之前的努力也是助力。当然面向工资编程也是一种取舍,笔者思考的时候也会陷入这个怪圈,所以希望在不断的学习和实践中,能够让自己更满意吧。

著名的 DRY 原则告诉我们 —— Don't repeat yourself,而笔者想更进一步的是,Deep Dive And Wide Mind,深入更多和尝试更多。

奇怪的前言就此结束。

作为最新的正式版本,虽然版本号只是小小的提升,但是 .NET Core 2.1 相比 .NET Core 2.0 在性能上又有了大大的提升。无论是项目构建速度,还是字符串操作,网络传输和 JIT 内联方法性能,可以这么说的是,如今的 .NET Core 已经主动为开发者带来抠到字节上的节省体验。具体的介绍还请参看 Performance Improvements in .NET Core 2.1 。

而在这篇文章里,笔者要聊聊的只是关于 async/await 的一些底层原理和 .NET Core 2.1 在异步操作对象分配上的优化操作。

async/await 实现简介

熟悉异步操作的开发者都知道,async/await 的实现基本上来说是一个骨架代码(Template method)和状态机。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

从反编译器中我们可以窥见骨架方法的全貌。假设有这样一个示例程序

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;} }

为了能更好地显示编译代码,特地将异步操作分成两个方法来实现,即组成了一条异步操作链。这种“侵入性”传递对于开发其实是更友好的,当代码中的一部分采用了异步代码,整个传递链条上便不得不采用异步这样一种正确的方式。接下来让我们看看编译器针对上述异步方法生成的骨架方法和状态机(也已经经过美化产生可读的C#代码)。

[DebuggerStepThrough]
[AsyncStateMachine((typeof(CallMethodAsyncStateMachine)]
private static Task<int> CallMethodAsync(string arg)
{CallMethodAsyncStateMachine stateMachine = new CallMethodAsyncStateMachine {arg = arg,builder = AsyncTaskMethodBuilder<int>.Create(),state = -1};stateMachine.builder.Start<CallMethodAsyncStateMachine>((ref stateMachine)=>{// 骨架方法启动第一次 MoveNext 方法stateMachine.MoveNext();});return stateMachine.builder.Task;
}[DebuggerStepThrough]
[AsyncStateMachine((typeof(MethodAsyncStateMachine)]
private static Task<int> MethodAsync(string arg)
{MethodAsyncStateMachine stateMachine = new MethodAsyncStateMachine {arg = arg,builder = AsyncTaskMethodBuilder<int>.Create(),state = -1};// 恢复委托函数Action __moveNext = () => {stateMachine.builder.Start<CallMethodAsyncStateMachine>(ref stateMachine);}__moveNext();return stateMachine.builder.Task;
}
  • MethodAsync/CallMethodAsync - 骨架方法

  • MethodAsyncStateMachine/CallMethodAsyncStateMachine - 每个 async 标记的异步操作都会产生一个骨架方法和状态机对象

  • arg - 显然原始代码上有多少个参数,生成的代码中就会有多少个字段

  • __moveNext - 恢复委托函数,对应状态机中的 MoveNext 方法,该委托函数会在执行过程中作为回调函数返回给对应Task的 Awaiter 从而使得 MoveNext 持续执行

  • builder - 该结构负责连接状态机和骨架方法

  • state - 始终从 -1 开始,方法执行时状态也是1,非负值代表一个后续操作的目标,结束时状态为 -2

  • Task - 代表当前异步操作完成后传播的任务,其内包含正确结果

可以看到,每个由 async 关键字标记的异步操作都会产生相应的骨架方法,而状态机也会在骨架方法中创建并运行。以下是实际的状态机内部代码,让我们用实际进行包含两步异步操作的 CallMethodAsyncStateMachine 做例子。

[CompilerGenerated]
private sealed class CallMethodAsyncStateMachine : IAsyncStateMachine{    public int state;  
 public string arg;  // 代表变量public AsyncTaskMethodBuilder<int> builder;    // 代表 resultprivate int result; // 代表 var result = await MethodAsync(arg);private Task<int> firstTaskToAwait;  // 代表 await Task.Delay(result);private Task secondTaskToAwait; private void MoveNext()    {    
        try{          
   switch (this.state) // 初始值为-1{        
          case -1: // 执行 await MethodAsync(arg);this.firstTaskToAwait = AsyncMethods.MethodAsync(this.arg);                    // 当 firstTaskToAwait 执行完毕this.result = firstTaskToAwait.Result;                    this.state = 0;                    // 调用 this.MoveNext();this.Builder.AwaitUnsafeOnCompleted(ref this.awaiter, ref this);              
           case 0:                
              // 执行 Task.Delay(result)this.secondTaskToAwait = Task.Delay(this.result);                    // 当 secondTaskToAwait 执行完毕this.state = 1;                    // 调用 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)    {} }

可以看到一个异步方法内含有几个异步方法,状态机便会存在几种分支判断情况。根据每个分支的执行情况,再通过调用 MoveNext 方法确保所有的异步方法能够完整执行。更进一步,看似是 switch 和 case 组成的分支方法,实质上仍然是一条异步操作执行和传递的Chain。

上述的 CallMethodAsync 方法也可以转化成以下 Task.ContinueWith 形式:

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; }

可以这样理解的是,总体看来,编译器每次遇到 await,当前执行的方法都会将方法的剩余部分注册为回调函数(当前 await 任务完成后接下来要进行的工作,也可能包含 await 任务,仍然可以顺序嵌套),然后立即返回(return builder.Task)。 剩余的每个任务将以某种方式完成其操作(可能被调度到当前线程上作为事件运行,或者因为使用了 I/O 线程执行,或者在单独线程上继续执行,这其实并不重要),只有在前一个 await 任务标记完成的情况下,才能继续进行下一个 await 任务。有关这方面的奇思妙想,请参阅《通过 Await 暂停和播放》

.NET Core 2.1 性能提升

上节关于编译器生成的内容并不能完全涵盖 async/await 的所有实现概念,甚至只是其中的一小部分,比如笔者并没有提到可等待模式(IAwaitable)和执行上下文(ExecutionContext)的内容,前者是 async/await 实现的指导原则,后者则是实际执行异步代码,返回给调用者结果和线程同步的操控者。包括生成的状态机代码中,当第一次执行发现任务并未完成时(!awaiter.isCompleted),任务将直接返回。

主要原因便是这些内容讲起来怕是要花很大的篇幅,有兴趣的同学推荐去看《深入理解C#》和 ExecutionContext。

异步代码能够显著提高服务器的响应和吞吐性能。但是通过上述讲解,想必大家已经认识到为了实现异步操作,编译器要自动生成大量的骨架方法和状态机代码,应用通常也要分配更多的相关操作对象,线程调度同步也是耗时耗力,这也意味着异步操作运行性能通常要比同步代码要差(这和第一句的性能提升并不矛盾,体重更大的人可能速度降低了,但是抗击打能力也更强了)。

但是框架开发者一直在为这方面的提升作者努力,最新的 .NET Core 2.1 版本中也提到了这点。原本的应用中,一个基于 async/await 操作的任务将分配以下四个对象:

  1. 返回给调用方的Task
    任务实际完成时,调用方可以知道任务的返回值等信息

  2. 装箱到堆上的状态机信息
    之前的代码中,我们用了ref标识一开始时,状态机实际以结构的形式存储在栈上,但是不可避免的,状态机运行时,需要被装箱到堆上以保留一些运行状态

  3. 传递给Awaiter的委托
    即前文的_moveNext,当链中的一个 Task 完成时,该委托被传递到下一个 Awaiter 执行 MoveNext 方法。

  4. 存储某些上下文(如ExecutionContext)信息的状态机执行者(MoveNextRunner)

据 Performance Improvements in .NET Core 2.1 一文介绍:

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

当前的应用将分配下图中的对象:

640?wx_fmt=png

而在 .NET Core 2.1 中,最终的分配对象将只有:

640?wx_fmt=png

四个分配对象最终减少到一个,分配空间也缩减到了过去的一半。更多的实现信息可以参考 Avoid async method delegate allocation。

结语

本文主要介绍了 async/await 的实现和 .NET Core 2.1 中关于异步操作性能优化的相关内容。因为笔者水平一般,文章篇幅有限,不能尽善尽美地解释完整,还希望大家见谅。

无论是在什么平台上,异步操作都是重要的组成部分,而笔者觉得任何开发者在会用之余,都应该更进一步地适当了解背后的故事。具体发展中,C# 借鉴了 F#中的异步实现,其他语言诸如 js 可能也借鉴了 C# 中的部分内容,当然一些基本术语,比如回调或是 feature,任何地方都是相似的,怎么都脱离不开计算机体系,这也说明了编程基础的重要性。

参考文献

  1. 通过 Await 暂停和播放

  2. 通过新的 Visual Studio Async CTP 更轻松地进行异步编程


原文地址: https://www.cnblogs.com/Wddpct/p/9002242.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321350.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Swashbuckle构建RESTful风格文档

本次和大家分享的是Swagger to WebApi的nuget包Swashbuckle&#xff1b;因为项目需要统一api文档的风格&#xff0c;并要支持多种开发语言&#xff08;C#&#xff0c;java&#xff0c;python&#xff09;&#xff0c;所以首先想到的是swagger来构建api文档&#xff0c;本章讲解…

【dfs】【bfs】【链表】 求连通分量 (ssl 1759)

求连通分量 ssl 1759 题目大意 由n个点组成的无向图&#xff0c;求连通在一起的点数最大是多少 原题 求一个图的连通分量 Input n 顶点数(<100) 边 Output 连通分量 Sample Input 8 6 3 1 2 2 5 5 4 4 1 8 7 0 0 Sample Output 4 方法一&#xff08;dfs …

发布 Rafy .NET Standard 版本 Nuget 包

去年年中&#xff0c;Rafy 框架的源码就已经支持了 Net Standard 2.0 版本。其开源代码也已经上传到 Github 中&#xff1a;https://github.com/zgynhqf/rafy/tree/NetStandard2.0 。但是这都只是在源码层面支持 NS2.0&#xff0c;并没有发布其正式的 Nuget 包。要使用这个版本…

你关心才值得分享 | K8S网络安全之访问控制技术实践

(请允许我插播下广告&#xff0c;便于其它伙伴了解趣码 Cloud Coder)还是那句话&#xff0c;你关心才值得分享~最近的一起分享就在5.10本周四晚&#xff0c;精彩千万不要错过&#xff01;Hi&#xff0c;你是不是也曾觉得K8S&#xff08; Kubernetes &#xff09;网络安全话题范…

从Xamarin.Essentials谈Xamarin库的封装

编者语&#xff1a;Xamarin在国内的推广还需要努力&#xff0c;其实这真的是移动端开发的一大福音&#xff0c;毕竟用一份代码的时间可以生成iOS/Android/Windows/Linux/macOS/Tizen多个平台&#xff0c;而且是原生的性能。Xamarin在Build 2018发布的新功能有Xamarin.Essential…

【最短路】【图论】【Floyed】牛的旅行(ssl 1119/luogu 1522)

牛的旅行 ssl 1119 luogu 1522 题目大意 有两堆点&#xff0c;每一堆点之中的任何两个点都一定有相连的路线&#xff0c;连接两堆点中的各一个点&#xff0c;使最远的两个点的距离最短 原题 农民John的农场里有很多牧区。有的路径连接一些特定的牧区。一片所有连通的牧区称…

用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识

什么是RESTREST 是 Representational State Transfer 的缩写. 它是一种架构的风格, 这种风格基于一套预定义的规则, 这些规则描述了网络资源是如何定义和寻址的.一个实现了REST这些规则的服务就叫做RESTful的服务.最早是由Roy Fielding提出的.RPC 风格/getUsers/getUser?id1/c…

使用ML.NET预测纽约出租车费

有了上一篇《.NET Core玩转机器学习》打基础&#xff0c;这一次我们以纽约出租车费的预测做为新的场景案例&#xff0c;来体验一下回归模型。场景概述我们的目标是预测纽约的出租车费&#xff0c;乍一看似乎仅仅取决于行程的距离和时长&#xff0c;然而纽约的出租车供应商对其他…

使用ML.NET实现情感分析[新手篇]

在发出《.NET Core玩转机器学习》和《使用ML.NET预测纽约出租车费》两文后&#xff0c;相信读者朋友们即使在不明就里的情况下&#xff0c;也能按照内容顺利跑完代码运行出结果&#xff0c;对使用.NET Core和ML.NET&#xff0c;以及机器学习的效果有了初步感知。得到这些体验后…

潘正磊:再过三五年 AI会变成开发人员的基本概念

在微软Build 2018开发者大会上&#xff0c;微软公司全球开发平台事业部的资深副总裁潘正磊&#xff08;Julia Liuson&#xff09;接受了界面记者在内的采访。潘正磊在微软西雅图总部带领一千多人组成的团队&#xff0c;微软的开发工具&#xff0c;包括Visual Studio&#xff0c…

qMISPlat入门级使用问题解答一

qMISPlat 2.0(业务配置开发平台) 自2018-4-18号正式开源以来&#xff0c;得到了众多.net core爱好者的关注&#xff0c;现将近半个月以来&#xff0c;大家反馈的一些使用配置方面的问题统一作如下解答。如你对qMISPlat不了解&#xff0c;请查看文章qMISPlat产品介绍。一、从码云…

【模拟】游戏(jzoj 1614)

游戏 题目大意&#xff1a; 有一个n*n的棋盘&#xff0c;有一个坐标在x,y的棋子&#xff0c; 1、2号玩家可以将他向左&#xff0c;向下&#xff0c;向左下&#xff08;45∘45^{\circ}45∘&#xff09;移动若干格&#xff0c;假如他们都是AKIOI聪明绝顶的巨佬&#xff0c;请问…

P4593-[TJOI2018]教科书般的亵渎【拉格朗日差值】

正题 题目链接:https://www.luogu.com.cn/problem/P4593 题目大意 场上有若干只怪&#xff0c;最高的为nnn&#xff0c;每个怪血量不同&#xff0c;有mmm个血量不存在。 不停释放亵渎&#xff08;全场打一&#xff0c;如果有怪死亡就再次生效&#xff09;&#xff0c;每次一…

如何创建一个基于 MSBuild Task 的跨平台的 NuGet 工具包

MSBuild 的 Task 为我们扩展项目的编译过程提供了强大的扩展性&#xff0c;它使得我们可以用 C# 语言编写扩展&#xff1b;利用这种扩展性&#xff0c;我们可以为我们的项目定制一部分的编译细节。NuGet 为我们提供了一种自动导入 .props 和 .targets 的方法&#xff0c;同时还…

Platform.Uno介绍

编者语&#xff1a;Xamarin国内很多人说缺乏可用的实例&#xff0c;我在写书过程中在完善一些常用场景的例子&#xff0c;希望帮到大家。Build 2018结束一周了&#xff0c;善友问我要不要谈谈Xamarin的一些变化&#xff0c;但碍于时间有限一直没有付诸行动。想想总得写点什么给…

ASP.NET Core amp; Docker 实战经验分享

一.前言最近一直在研究和实践ASP.NET Core、Docker、持续集成。在ASP.NET Core 和 Dcoker结合下遇到了一些坑&#xff0c;在此记录和分享&#xff0c;希望对大家有一些帮助。二.中间镜像我前面写过一个 《ASP.NET Core & Docker 零基础持续集成 》的教程。里面我们通过持续…

【图论】【最短路】【SPFA】香甜的黄油 Sweet Butter (luogu 1828)

香甜的黄油 Sweet Butter luogu 1828 题目大意&#xff1a; 有n头奶牛&#xff0c;他们在不同的牧场中&#xff0c;他们之间有一些路&#xff0c;现在要让他们去一个地方吃黄油&#xff0c;使他们的总距离最小 题目描述 农夫John发现做出全威斯康辛州最甜的黄油的方法&…

【dfs】简单游戏(jzoj 2121)

简单游戏 题目大意 原本有n个数字&#xff0c;第1,2个相加&#xff0c;第2&#xff0c;3个相加……第n-1,n个相加&#xff0c;由此得出一个长度为n-1的新序列&#xff0c;然后不停重复&#xff0c;最后得出一个t&#xff0c;现在给出一开始的n和t求符合的序列&#xff08;字典…

[翻译] 比较 Node.js,Python,Java,C# 和 Go 的 AWS Lambda 性能

原文: Comparing AWS Lambda performance of Node.js, Python, Java, C# and GoAWS 最近宣布他们支持了 C&#xff03; (Net Core 2.0 版本) 和 Go 语言来实现 Lambda 功能。(译者注: AWS Lambda 是 AWS 推出的 Serverless 功能&#xff0c;请参阅这里或 Serverless 相关资料)做…

【结论】立体井字棋(jzoj 2124)

立体井字棋 题目大意&#xff1a; 在一个nnn的正方体中&#xff0c;由n个格子连成一条直线的方案数&#xff08;多少种可能用n个格子连成一条直线&#xff09; 样例输入 2 样例输出 28 数据范围限制 对于30%的数据&#xff0c; n<10&#xff1b; 对于100%的数据&am…