【转】探索c#之Async、Await剖析

阅读目录:

  1. 基本介绍
  2. 基本原理剖析
  3. 内部实现剖析
  4. 重点注意的地方
  5. 总结

基本介绍

Async、Await是net4.x新增的异步编程方式,其目的是为了简化异步程序编写,和之前APM方式简单对比如下。

APM方式,BeginGetRequestStream需要传入回调函数,线程碰到BeginXXX时会以非阻塞形式继续执行下面逻辑,完成后回调先前传入的函数。

    HttpWebRequest myReq =(HttpWebRequest)WebRequest.Create("http://cnblogs.com/");myReq.BeginGetRequestStream();//to do

Async方式,使用Async标记Async1为异步方法,用Await标记GetRequestStreamAsync表示方法内需要耗时的操作。主线程碰到await时会立即返回,继续以非阻塞形式执行主线程下面的逻辑。当await耗时操作完成时,继续执行Async1下面的逻辑

static async void Async1(){HttpWebRequest myReq = (HttpWebRequest)WebRequest.Create("http://cnblogs.com/");await myReq.GetRequestStreamAsync();//to do}     

上面是net类库实现的异步,如果要实现自己方法异步。
APM方式:

        public delegate int MyDelegate(int x);   MyDelegate mathDel = new MyDelegate((a) => { return 1; });mathDel.BeginInvoke(1, (a) => { },null);

Async方式:

static async void Async2(){await Task.Run(() => { Thread.Sleep(500); Console.WriteLine("bbb"); });Console.WriteLine("ccc");}Async2();Console.WriteLine("aaa");

对比下来发现,async/await是非常简洁优美的,需要写的代码量更少,更符合人们编写习惯。
因为人的思维对线性步骤比较好理解的。

APM异步回调的执行步骤是:A逻辑->假C回调逻辑->B逻辑->真C回调逻辑,这会在一定程度造成思维的混乱,当一个项目中出现大量的异步回调时,就会变的难以维护。
Async、Await的加入让原先这种混乱的步骤,重新拨正了,执行步骤是:A逻辑->B逻辑->C逻辑。

基本原理剖析

作为一个程序员的自我修养,刨根问底的好奇心是非常重要的。 Async刚出来时会让人有一头雾水的感觉,await怎么就直接返回了,微软怎么又出一套新的异步模型。那是因为习惯了之前的APM非线性方式导致的,现在重归线性步骤反而不好理解。 学习Async时候,可以利用已有的APM方式去理解,以下代码纯属虚构
比如把Async2方法想象APM方式的Async3方法:

static async void Async3(){var task= await Task.Run(() => { Thread.Sleep(500); Console.WriteLine("bbb"); });//注册task完成后回调task.RegisterCompletedCallBack(() =>{Console.WriteLine("ccc");});}

上面看其来就比较好理解些的,再把Async3方法想象Async4方法:

static  void Async4(){var thread = new Thread(() =>{Thread.Sleep(500);Console.WriteLine("bbb");});//注册thread完成后回调thread.RegisterCompletedCallBack(() =>{Console.WriteLine("ccc");});thread.Start();}

这样看起来就非常简单明了,连async都去掉了,变成之前熟悉的编程习惯。虽然代码纯属虚构,但基本思想是相通的,差别在于实现细节上面。

内部实现剖析

作为一个程序员的自我修养,严谨更是不可少的态度。上面的基本思想虽然好理解了,但具体细节呢,编程是个来不得半点虚假的工作,那虚构的代码完全对不住看官们啊。

继续看Async2方法,反编译后的完整代码如下:

internal class Program
{// Methods[AsyncStateMachine(typeof(<Async2>d__2)), DebuggerStepThrough]private static void Async2(){<Async2>d__2 d__;d__.<>t__builder = AsyncVoidMethodBuilder.Create();d__.<>1__state = -1;d__.<>t__builder.Start<<Async2>d__2>(ref d__);}private static void Main(string[] args){Async2();Console.WriteLine("aaa");Console.ReadLine();}// Nested Types[CompilerGenerated]private struct <Async2>d__2 : IAsyncStateMachine{// Fieldspublic int <>1__state;public AsyncVoidMethodBuilder <>t__builder;private object <>t__stack;private TaskAwaiter <>u__$awaiter3;// Methodsprivate void MoveNext(){try{TaskAwaiter awaiter;bool flag = true;switch (this.<>1__state){case -3:goto Label_00C5;case 0:break;default:if (Program.CS$<>9__CachedAnonymousMethodDelegate1 == null){Program.CS$<>9__CachedAnonymousMethodDelegate1 = new Action(Program.<Async2>b__0);}awaiter = Task.Run(Program.CS$<>9__CachedAnonymousMethodDelegate1).GetAwaiter();if (awaiter.IsCompleted){goto Label_0090;}this.<>1__state = 0;this.<>u__$awaiter3 = awaiter;this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<Async2>d__2>(ref awaiter, ref this);flag = false;return;}awaiter = this.<>u__$awaiter3;this.<>u__$awaiter3 = new TaskAwaiter();this.<>1__state = -1;Label_0090:awaiter.GetResult();awaiter = new TaskAwaiter();Console.WriteLine("ccc");}catch (Exception exception){this.<>1__state = -2;this.<>t__builder.SetException(exception);return;}Label_00C5:this.<>1__state = -2;this.<>t__builder.SetResult();}[DebuggerHidden]private void SetStateMachine(IAsyncStateMachine param0){this.<>t__builder.SetStateMachine(param0);}}public delegate int MyDelegate(int x);
}Collapse Methods

View Code

发现async、await不见了,原来又是编译器级别提供的语法糖优化,所以说async不算是全新的异步模型。 可以理解为async更多的是线性执行步骤的一种回归,专门用来简化异步代码编写。
从反编译后的代码看出编译器新生成一个继承IAsyncStateMachine 的状态机结构asyncd(代码中叫<Async2>d__2,后面简写AsyncD),下面是基于反编译后的代码来分析的

IAsyncStateMachine最基本的状态机接口定义:

public interface IAsyncStateMachine
{void MoveNext();void SetStateMachine(IAsyncStateMachine stateMachine);
}

既然没有了async、await语法糖的阻碍,就可以把代码执行流程按线性顺序来理解,其整个执行步骤如下:

1. 主线程调用Async2()方法
2. Async2()方法内初始化状态机状态为-1,启动AsyncD
3. MoveNext方法内部开始执行,其task.run函数是把任务扔到线程池里,返回个可等待的任务句柄。MoveNext源码剖析:

//要执行任务的委托

 Program.CS$<>9__CachedAnonymousMethodDelegate1 = new Action(Program.<Async2>b__0);

//开始使用task做异步,是net4.0基于任务task的编程方式。

 awaiter =Task.Run(Program.CS$<>9__CachedAnonymousMethodDelegate1).GetAwaiter();

//设置状态为0,以便再次MoveNext直接break,执行switch后面的逻辑,典型的状态机模式。

this.<>1__state = 0;

//返回调用async2方法的线程,让其继续执行主线程后面的逻辑

this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<Async2>d__2>(ref awaiter, ref this);
return;

4. 这时就已经有2个线程在跑了,分别是主线程和Task.Run在跑的任务线程。

5. 执行主线程后面逻辑输出aaa,任务线程运行完成后输出bbb、在继续执行任务线程后面的业务逻辑输出ccc。

Label_0090: 
awaiter.GetResult(); 
awaiter = new TaskAwaiter();
Console.WriteLine("ccc");

这里可以理解为async把整个主线程同步逻辑,分拆成二块。 第一块是在主线程直接执行,第二块是在任务线程完成后执行, 二块中间是任务线程在跑,其源码中awaiter.GetResult()就是在等待任务线程完成后去执行第二块。
从使用者角度来看执行步骤即为: 主线程A逻辑->异步任务线程B逻辑->主线程C逻辑。

        Test();Console.WriteLine("A逻辑");static async void Test(){await Task.Run(() => { Thread.Sleep(1000); Console.WriteLine("B逻辑"); });Console.WriteLine("C逻辑"); }

回过头来对比下基本原理剖析小节中的虚构方法Async4(),发现区别在于一个是完成后回调,一个是等待完成后再执行,这也是实现异步最基本的两大类方式。

重点注意的地方

主线程A逻辑->异步任务线程B逻辑->主线程C逻辑。

注意:这3个步骤是有可能会使用同一个线程的,也可能会使用2个,甚至3个线程。 可以用Thread.CurrentThread.ManagedThreadId测试下得知。

     Async7();Console.WriteLine(Thread.CurrentThread.ManagedThreadId); static async void Async7(){await Task.Run(() =>{Console.WriteLine(Thread.CurrentThread.ManagedThreadId); });Console.WriteLine(Thread.CurrentThread.ManagedThreadId); }

正由于此,才会有言论说Async不用开线程,也有说需要开线程的,从单一方面来讲都是对的,也都是错的。 上面源码是从简分析的,具体async内部会涉及到线程上下文切换,线程复用、调度等。 想深入的同学可以研究下ExecutionContextSwitcher、 SecurityContext.RestoreCurrentWI、ExecutionContext这几个东东。

其实具体的物理线程细节可以不用太关心,知道其【主线程A逻辑->异步任务线程B逻辑->主线程C逻辑】这个基本原理即可。 另外Async也会有线程开销的,所以要合理分业务场景去使用。

总结

从逐渐剖析Async中发现,Net提供的异步方式基本上一脉相承的,如:
1. net4.5的Async,抛去语法糖就是Net4.0的Task+状态机。
2. net4.0的Task, 退化到3.5即是(Thread、ThreadPool)+实现的等待、取消等API操作。

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

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

相关文章

时间计算题100道_小学数学专项练习:计算题200道,趁早打印给孩子,期末考试拿100分!...

点击上方「升学的秘诀」关注我们&#xff01;获取更多教育经验、方法、学习资料等&#xff0c;每天中午12点与您相约&#xff01;▼说到我们数学学习过程中最重要的是什么&#xff1f;毋庸置疑那就是我们的一个计算能力&#xff0c;计算可以说是贯穿了我们整个数学学习体系&…

幂等性实现 -接口幂等性

接口幂等性 1.什么是幂等性 对于同一笔业务操作&#xff0c;不管调用多少次&#xff0c;得到的结果都是一样的。 也就是方法调用一次和调用多次产生的额外效果是相同的&#xff0c;他就具有幂等性 2.为什么需要幂等性 在系统高并发的环境下&#xff0c;很有可能因为网络&#…

C 怎么读取Cpp文件_python之调用C加速计算(一)

一、前言python语言是目前比较火的语言&#xff0c;很容易上手&#xff0c;对数据处理也比较友好&#xff0c;可以用几行代码就能进行一些简单的数据处理工作。但是对于稍微大型的数值计算&#xff0c;或者一些涉及到大量循环的数值计算python的计算速度有点让人失望。即使是使…

【转】刨根究底字符编码【2.0版】(3):字符编码的由来、演变与ASCII码

为什么需要字符编码 1. 计算机一开始发明出来时是用来解决数字计算问题的&#xff0c;后来人们发现&#xff0c;计算机还可以做更多的事&#xff0c;例如文本处理。 但计算机其实挺“笨”的&#xff0c;它只“认识”010110111000…这样由0和1两个数字组成的二进制数字&#…

JS创建对象的模式介绍

转自http://www.cnblogs.com/asqq/archive/2013/02/01/3194993.html

matplotlib的优点_超详细matplotlib基础介绍!!!

(给Python开发者加星标&#xff0c;提升Python技能)来源&#xff1a;逐梦erhttps://zhumenger.blog.csdn.net/article/details/106530281【导语】&#xff1a;出色的数据可视化&#xff0c;会让你的数据分析等工作锦上添花&#xff0c;让人印(升)象(职)深(加)刻(薪)。matplotli…

【转】WPF PRISM开发入门一( 初始化PRISM WPF程序)

这篇博客将介绍在WPF项目中引入PRISM框架进行开发的一些基础知识。目前最新的PRISM的版本是Prism 6.1.0&#xff0c;可以在Github上获取PRISM的源码。这个系列的博客将选择PRISM 4.1版本来讲解。可以从微软官网上下载到PRISM 4.1相关内容。将下载下来的文件解压开&#xff1a; …

截屏悬浮软件_功能强大,却小巧的录屏软件,不在错过你的王者时刻

看看录屏是一款操作简单。功能强大的录屏软件。他可以设置你录制视频的一个分辨率&#xff0c;帧率以及录制屏幕方向&#xff0c;非常方便&#xff0c;用户将手机摇一摇就可以控制开启和停止录屏&#xff0c;高效录制精彩瞬间&#xff0c;在录制游戏视频的时候也可以做到不掉帧…

公司用的非标普通自动化用单片机还是plc_PLC的介绍

PLC又叫可编程控制器&#xff0c;一开始是替代传统接触器的一个东西。随着人工价格不断的上涨&#xff0c;自动化的设备会越来越普及。自动化不再是大企业才用的起的东西 &#xff0c;各种多元化小型自动化设备进入了普通小企业甚至家庭作坊。PLC其实是单片机开发出来的一种工业…

比较文本差异的工具_Linux 开发的五大必备工具 | Linux 中国

Linux 已经成为工作、娱乐和个人生活等多个领域的支柱&#xff0c;人们已经越来越离不开它。在 Linux 的帮助下&#xff0c;技术的变革速度超出了人们的想象&#xff0c;Linux 开发的速度也以指数规模增长。因此&#xff0c;越来越多的开发者也不断地加入开源和学习 Linux 开发…

【转】C# 动态对象(dynamic)的用法

说到正确用法&#xff0c;那么首先应该指出一个错误用法&#xff1a; 常有人会拿var这个关键字来和dynamic做比较。实际上&#xff0c;var和dynamic完全是两个概念&#xff0c;根本不应该放在一起做比较。var实际上是编译期抛给我们的“语法糖”&#xff0c;一旦被编译&#x…

关于prototype使用位置问题的讨论

问题贴&#xff1a;http://bbs.csdn.net/topics/390446362 new四部曲&#xff1a; &#xff08;1&#xff09;创建一个新的对象&#xff0c;并让函数的 this 指针指向它&#xff1b; &#xff08;2&#xff09;将函数的 prototype 对象的所有成员都赋给这个新对象&#xff0c…

@query传参_vue-router中params传参和query传参的区别及处理方法

在 Vue 实例内部&#xff0c;你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push想要导航到不同的 URL&#xff0c;则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录&#xff0c;所以&#xff0c;当用户点击浏览器后退按钮时&#xff0c;则…

JS成员函数声明位置优化

上代码 function A() {this.a function(){}; } a1 new A(); a2 new A(); alert( a1.aa2.a);输出 说明了a1.a&#xff0c;a2.a指向的内存不是同一个&#xff0c;也就是每个对象都有一份自己的函数&#xff0c;只不过一个类的所有实例之间的函数长得是一样的&#xff01; 所以…

mybatis plus 事务管理器_SpringBoot第七篇:springboot开启声明式事务

springboot开启事务很简单&#xff0c;只需要一个注解Transactional 就可以了。因为在springboot中已经默认对jpa、jdbc、mybatis开启了事事务&#xff0c;引入它们依赖的时候&#xff0c;事物就默认开启。当然&#xff0c;如果你需要用其他的orm&#xff0c;比如beatlsql&…

JS静态变量和静态函数

function A(){this.id "我是AA"} // 在构造函数外定义的都是所有对象共享的 A.id "我是A"; A.sayId function(){alert(A.id);} A.sayId(); 如上&#xff0c;在构造函数外用函数名定义的属性或者方法&#xff0c;可以也只可以通过函数名来访问&…

Spark读取HDFS上的Snappy压缩文件所导致的内存溢出问题 java.lang.OutOfMemoryError: GC overhead limit exceeded

报错java.lang.OutOfMemoryError: GC overhead limit exceeded HDFS上有一些每天增长的文件&#xff0c;使用Snappy压缩&#xff0c;突然某天OOM了 1.原因: 因为snappy不能split切片&#xff0c;也就会导致一个文件将会由一个task来读取&#xff0c;读取后解压&#xff0c;数…

【转】VS编程,快速折叠或者展开代码到 #region 级别的设置方法。

在代码比较多的文档中&#xff0c;使用#region进行分功能的区分折叠是一个方便的方法。 如果文档中含有很多个#region标签&#xff0c;想一次全部折叠或者展开&#xff0c;有时是必要的。 这里给出一种设置方法&#xff0c;适用于VS2019&#xff0c;其它VS版本请自己验证。 1、…

.net一个函数要用另一个函数的值_Mysql:条件判断函数-CASE WHEN、IF、IFNULL详解

前言在众多SQL中&#xff0c;统计型SQL绝对是让人头疼的一类&#xff0c;之所以如此&#xff0c;是因为这种SQL中必然有大量的判读对比。而条件判断函数就是应对这类需求的利器。本文重点总结CASE WHEN、IF、IFNULL三种函数。1 CASE WHENCase when语句能在SQL语句中织入判断逻辑…