C# 5.0新特性:Async和Await使异步编程更简单_第1页
C# 5.0新特性:Async和Await使异步编程更简单_第2页
C# 5.0新特性:Async和Await使异步编程更简单_第3页
C# 5.0新特性:Async和Await使异步编程更简单_第4页
C# 5.0新特性:Async和Await使异步编程更简单_第5页
已阅读5页,还剩6页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、在之前的C#基础知识系列文章中只介绍了从C#1.0到C#4.0中主要的特性,然而.NET4.5的推出,对于C#又有了新特性的增加就是C#5.0中async和await两个关键字,这两个关键字简化了异步编程,之所以简化了,还是因为编译器给我们做了更多的工作,下面就具体看看编译器到底在背后帮我们做了哪些复杂的工作的。AD:2013大数据全球技术峰会课程PPT下载一、引言在之前的C#基础知识系列文章中只介绍了从C#1.0到C#4.0中主要的特性,然而.NET4.5的推出,对于C#又有了新特性的增加就是C#5.0中async和await两个关键字,这两个关键字简化了异步编程,之所以简化了,还是因为编译

2、器给我们做了更多的工作,下面就具体看看编译器到底在背后帮我们做了哪些复杂的工作的。二、同步代码存在的问题对于同步的代码,大家肯定都不陌生,因为我们平常写的代码大部分都是同步的,然而同步代码却存在一个很严重的问题,例如我们向一个Web服务器发出一个请求时,如果我们发出请求的代码是同步实现的话,这时候我们的应用程序就会处于等待状态,直到收回一个响应信息为止,然而在这个等待的状态,对于用户不能操作任何的UI界面以及也没有任何的消息,如果我们试图去操作界面时,此时我们就会看到应用程序为响应的信息(在应用程序的窗口旁),相信大家在平常使用桌面软件或者访问web的时候,肯定都遇到过这样类似的情况的,对于这

3、个,大家肯定会觉得看上去非常不舒服。引起这个原因正是因为代码的实现是同步实现的,所以在没有得到一个响应消息之前,界面就成了一个卡死状态了,所以这对于用户来说肯定是不可接受的,因为如果我要从服务器上下载一个很大的文件时,此时我们甚至不能对窗体进行关闭的操作的。为了具体说明同步代码存在的问题(造成界面开始),下面通过一个程序让大家更形象地看下问题所在:/单击事件privatevoidbtnClick_Click(objectsender,EventArgse)this.btnClick.Enabled=false;longlength=AccessWeb();this.btnClick.Enabl

4、ed=true;/这里可以做一些不依赖回复的操作OtherWork();this.richTextBox1.Text+=String.Format(n回复的字节长度为:length);txbMainThreadID.Text=Thread.CurrentThread.ManagedThreadId.ToString();privatelongAccessWeb()MemoryStreamcontent=newMemoryStream();0.rn,/对MSDN发起一个Web请求HttpWebRequestwebRequestWebRequest.Create(if(webRequest!=nu

5、ll)/返回回复结果using(WebResponseresponse=webRequest.GetResponse()using(StreamresponseStream=response.GetResponseStream()responseStream.CopyTo(content);txbAsynMethodID.Text=Thread.CurrentThread.ManagedThreadId.ToString();returncontent.Length;=运行程序后,当我们点击窗体的点击我按钮之后,在得到服务器响应之前,我们不能对窗体进行任何的操作,包括移动窗体,关闭窗体等,具体

6、运行结果如下:三、传统的异步编程来改善程序的响应上面部分我们已经看到同步方法所带来的实际问题了,为了解决类似的问题,.NETFramework很早就提供了对异步编程的支持,下面就用.NET1.0中提出的异步编程模型(APM)来解决上面的问题,http:/www.321321.cc/具体代码如下(注释的部分通过获得GUI线程的同步上文对象,然后同步调用同步上下文对象的post方法把要调用的方法交给GUI线程去处理,因为控件本来就是由GUI线程创建的,然后由它自己执行访问控件的操作就不存在跨线程的问题了,程序中使用的是调用RichTextBox控件的Invoke方式来异步回调访问控件的方法,其实背

7、后的原来和注释部分是一样的,调用RichTextBox控件的Invoke方法可以获得创建RichTextBox控件的线程信息(也就是前一种方式的同步上下文),然后让Invoke回调的方法在该线程上运行):privatevoidbtnClick_Click(objectsender,EventArgse)this.richTextBox1.Clear();btnClick.Enabled=false;AsyncMethodCallercaller=newAsyncMethodCaller(TestMethod);IAsyncResultresult=caller.BeginInvoke(GetR

8、esult,null);/捕捉调用线程的同步上下文派生对象/sc=SynchronizationContext.Current;#region使用APM实现异步编程/同步方法privatestringTestMethod()/模拟做一些耗时的操作/实际项目中可能是读取一个大文件或者从远程服务器中获取数据等。for(inti=0;i10;i+)Thread.Sleep(200);return点击我按钮事件完成;/回调方法privatevoidGetResult(IAsyncResultresult)AsyncMethodCallercaller=(AsyncMethodCaller)(Async

9、Result)result).AsyncDelegate;/调用EndInvoke去等待异步调用完成并且获得返回值/如果异步调用尚未完成,则EndInvoke会一直阻止调用线程,直到异步调用完成stringresultvalue=caller.EndInvoke(result);/sc.Post(ShowState,resultvalue);richTextBox1.Invoke(showStateCallback,resultvalue);/显示结果到richTextBoxprivatevoidShowState(objectresult)richTextBox1.Text=result.T

10、oString();btnClick.Enabled=true;/显示结果到richTextBox/privatevoidShowState(stringresult)/richTextBox1.Text=result;/btnClick.Enabled=true;/#endregion运行的结果为:上面部分演示了使用传统的异步编程模型(APM)来解决同步代码所存在的问题,然而在.NET2.0,.NET4.0和.NET4.5中,微软都有推出新的方式来解决同步代码的问题,他们分别为基于事件的异步模式,基于任务的异步模式和提供async和await关键字来对异步编程支持。关于前两种异步编程模式,在

11、我前面的文章中都有介绍,大家可以查看相关文章进行详细地了解,本部分就C#5.0中的async和await这两个关键字如何实现异步编程的问题来给大家介绍下。下面通过代码来了解下如何使用async和await关键字来实现异步编程,并且大家也可以参看前面的博客来对比理解使用async和await是异步编程更简单。privateasyncvoidbtnClick_Click(objectsender,EventArgse)longlength=awaitAccessWebAsync();/这里可以做一些不依赖回复的操作OtherWork();this.richTextBox1.Text+=String

12、.Format(n回复的字节长度为:length);txbMainThreadID.Text=Thread.CurrentThread.ManagedThreadId.ToString();/从代码中可以看出C#5.0中定义异步方法就像定义同步方法一样简单。/使用async和await定义异步方法不会创建新线程,/它运行在现有线程上执行多个任务./此时不知道大家有没有一个疑问的?在现有线程上(即UI线程上)运行一个耗时的操作时,/为什么不会堵塞UI线程的呢?/这个问题的答案就是当编译器看到await关键字时,线程会privateasyncTaskAccessWebAsync()MemorySt

13、reamcontent=newMemoryStream();/对MSDN发起一个Web请求HttpWebRequestwebRequestWebRequest.Create(if(webRequest!=null)/返回回复结果using(WebResponseresponse=awaitwebRequest.GetResponseAsync()0.rn,=using(StreamresponseStream=response.GetResponseStream()awaitresponseStream.CopyToAsync(content);txbAsynMethodID.Text=Thr

14、ead.CurrentThread.ManagedThreadId.ToString();returncontent.Length;privatevoidOtherWork()this.richTextBox1.Text+=rn等待服务器回复中.n;运行结果如下:五、async和await关键字剖析我们对比下上面使用async和await关键字来实现异步编程的代码和在第二部分的同步代码,有没有发现使用async和await关键字的异步实现和同步代码的实现很像,只是异步实现中多了async和await关键字和调用的方法都多了async后缀而已。正是因为他们的实现很像,所以我在第四部分才命名为使用

15、async和await使异步编程更简单,就像我们在写同步代码一样,并且代码的coding思路也是和同步代码一样,这样就避免考虑在APM中委托的回调等复杂的问题,以及在EAP中考虑各种事件的定义。从代码部分我们可以看出async和await的使用确实很简单,我们就如在写同步代码一般,但是我很想知道编译器到底给我们做了怎样的处理的?并且从运行结果可以发现,运行异步方法的线程和GUI线程的ID是一样的,也就是说异步方法的运行在GUI线程上,所以就不用像APM中那样考虑跨线程访问的问题了(因为通过委托的BeginInvoke方法来进行回调方法时,回调方法是在线程池线程上执行的)。下面就用反射工具看看编

16、译器把我们的源码编译成什么样子的:对于按钮点击事件的代码来说,编译器生成的背后代码却是下面这样的,完全和我们源码中的两个样:/编译器为按钮Click事件生成的代码privatevoidbtnClick_Click(objectsender,EventArgse)d_0d_;d_.4_this=this;d_.sender=sender;d_.e=e;d_.t_builder=AsyncVoidMethodBuilder.Create();d_.1_state=-1;d_.t_builder.Startd_0(refd_);看到上面的代码,作为程序员的我想说编译器你怎么可以这样呢?怎么可以任意篡

17、改我的代码呢?这样不是侵犯我的版权了吗?你要改最起码应该告诉我一声吧,如果我的源码看到它在编译器中的实现是上面那样的,我相信我的源码会说难道我中了世间上最恶毒的面目全非脚吗?好吧,为了让大家更好地理清编译器背后到底做了什么事情,下面就顺着上面的代码摸瓜,我也来展示耍一套还我漂漂拳来帮助大家找到编译器代码和源码的对应关系。我的分析思路为:1、提出问题我的click事件的源码到哪里去了呢?从编译器代码我们可以看到,前面的7句代码都是对某个类进行赋值的操作,最真正起作用的就是最后Start方法的调用。这里又产生了几个疑问d_0是什么类型?该类型中的t_builder字段类型的Start方法到底是做什

18、么用的?有了这两个疑问,我们就点击d_0(反射工具可以让我们直接点击查看)来看看它是什么类型/d_0类型的定义,从下面代码可以看出它是一个结构体/该类型是编译器生成的一个嵌入类型/看到该类型的实现有没有让你联想到什么?privatestructd_0:IAsyncStateMachine/Fieldspublicint1_state;publicForm14_this;publicAsyncVoidMethodBuildert_builder;privateobjectt_stack;privateTaskAwaiteru_$awaiter2;publiclong5_1;publicEvent

19、Argse;publicobjectsender;/MethodsprivatevoidMoveNext()tryTaskAwaiterCS$0$0001;boolt_doFinallyBodies=true;switch(this.1_state)case-3:gotoLabel_010E;case0:break;default:/获取用于等待Task(任务)的等待者。你要知道某个任务是否完成,我们就需要一个等待者对象对该任务进行一个监控,所以微软就定义了一个等待者对象的/从这里可以看出,其实async和await关键字背后的实现原理是基于任务的异步编程模式(TAP)/这里代码是在线程池线程

20、上运行的CS$0$0001=this.4_this.AccessWebAsync().GetAwaiter();/如果任务完成就调转到Label_007A部分的代码if(CS$0$0001.IsCompleted)gotoLabel_007A;/设置状态为0为了退出回调方法。this.1_state=0;this.u_$awaiter2=CS$0$0001;/这个代码是做什么用的呢?让我们带着问题看下面的分析this.t_builder.AwaitUnsafeOnCompletedTaskAwaiter,Form1.d_0(refCS$0$0001,refthis);t_doFinallyBo

21、dies=false;/返回到调用线程,即GUI线程,这也是该方法不会堵塞GUI线程的原因,不管任务是否完成都返回到GUI线程return;/当任务完成时,不会执行下面的代码,会直接执行Label_007A中代码CS$0$0001=this.u_$awaiter2;this.u_$awaiter2=newTaskAwaiter();/为了使再次回调MoveNext代码this.1_state=-1;Label_007A:/下面代码是在GUI线程上执行的CS$0$0001=newTaskAwaiter();longCS$0$0003=CS$0$0001.GetResult();this.5_1=

22、CS$0$0003;/我们源码中的代码这里的this.4_this.OtherWork();this.4_this.richTextBox1.Text=this.4_this.richTextBox1.Text+string.Format(n回复的字节长度为:0.rn,this.5_1);this.4_this.txbMainThreadID.Text=Thread.CurrentThread.ManagedThreadId.ToString();catch(Exceptiont_ex)this.1_state=-2;this.t_builder.SetException(t_ex);retu

23、rn;Label_010E:this.1_state=-2;this.t_builder.SetResult();DebuggerHiddenprivatevoidSetStateMachine(IAsyncStateMachineparam0)this.t_builder.SetStateMachine(param0);如果你看过我的迭代器专题的话,相信你肯定可以联想到该结构体就是一个迭代器的一个实现,其主要方法就是MoveNext方法。从上面的代码的注释应该可以帮助我们解决在第一步提到的第一个问题,即d_0是什么类型,下面就分析下第二个问题,从d_0结构体的代码中可以发现t_builder

24、的类型是AsyncVoidMethodBuilder类型,下面就看看它的Start方法的解释运行关联状态机的生成器,即调用该方法就可以开始运行状态机,运行状态机指的就是执行MoveNext方法(MoveNext方法中有我们源码中所有代码,这样就把编译器生成的Click方法与我们的源码关联起来了)。从上面代码注释中可以发现,当该MoveNext被调用时会立即还回到GUI线程中,同时也有这样的疑问刚开始调用MoveNext方法时,任务肯定是还没有被完成的,但是我们输出我们源码中的代码,必须等待任务完成(因为任务完成才能调转到Label_007A中的代码),此时我们应该需要回调MoveNext方法来

25、检查任务是否完成,(就如迭代器中的,我们需要使用foreach语句一直调用MoveNext方法),然而我们在代码却没有找到回调的任何代码啊?对于这个疑问,回调MoveNext方法肯定是存在的,只是首次看上面代码的朋友还没有找到类似的语句而已,上面代码注释中我提到了一个问题这个代码是做什么用的呢?让我们带着问题看下面的分析,其实注释下面的代码就是起到回调MoveNext方法的作用,AsyncVoidMethodBuilder.AwaitUnsafeOnCompleted方法就是调度状态机去执行MoveNext方法,从而也就解决了回调MoveNext的疑问了。相信大家从上面的解释中可以找到源码与编

26、译器代码之间的对应关系了吧,但是我在分析完上面的之后,又有一个疑问当任务完成时,是如何退出MoveNext方法的呢?总不能让其一直回调吧,从上面的代码的注释可以看出,当任务执行完成之后,会把1_state设置为0,当下次再回调MoveNext方法时就会直接退出方法,然而任务没完成之前,同样也会把1_state设置为0,但是Switch部分后面的代码又把1_state设置为-1,这样就保证了在任务没完成之前,MoveNext方法可以被重复回调,当任务完成之后,1_state设置为-1的代码将不会执行,而是调转到Label_007A部分。经过上面的分析之后,相信大家也可以耍出一套还我漂漂拳去分析异步方法AccessWebAsync(),其分析思路是和btnClick_Click的分析思路是一样的.这里就不重复啰嗦了。分析完之后,下面再分享下几个关于async和await常问的

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论