zoukankan      html  css  js  c++  java
  • .NET&C#异步编程

    1.  演进过程

              本文档主要记录.net平台下异步编程不同时期的不同实现方案,.net平台异步编程经历了以下几次演变:

    1. Asynchronous Programming Model(APM):这种模式又被成为IAsyncResult模式,在.net1.0时提出,在同步方法中通过调用BeginXXXX和EndXXXX开头的方法对实现异步操作,此模式需要分配和回收IAsyncResult对象消耗资源降低效率,且不支持取消和没有提供进度报告的功能,微软不推荐使用。
    2. Event-based Asynchronous Pattern(EAP):它是基于事件模式的异步实现,在.net2.0时提出,这种模式具有一个或多个以Async为后缀的方法和Completed事件,它们都支持异步方法的取消、进度报告和报告结果,且其基于APM模式,此模式效率虽高,但.net中并不是所有类都支持,且业务复杂时就很难控制,微软不推荐使用。
    3. Task-based Asynchronous Pattern(TAP:task):它是基于任务模式的异步实现,在.net4.0时提出,这种模式有四种方法创建Task,1.Task.Factory.StartNew()2.(new Task(()=>{ //TODO })).Start()3.Task.Run()是.net4.5增加4.Task.FromResult(),微软推荐使用的。
    4. Task-based Asynchronous Pattern(TAP:async/await):它是基于任务模式的异步实现,在.net4.5时提出,它与第三种实现实质上相等,使用这两个关键字会使代码看起来与同步代码相当和简洁,进一步摒弃掉异步编程的复杂结构,微软极力推荐使用的异步编程模式。

    2.  模式:APM和EAP

    2.1.  APM

    本人在WCF时期应用APM模式调用服务使用最广泛,现在除了UI交互外很少使用APM模式,以下示例仅为展示APM编码模式

     1 public void Test(){
     2     var urlStr="http://www.test.com/test/testAPM";
     3     var request=HttpWebRequest.Create(url);
     4     request.BeginGetResponse(AsyncCallbackImpl,request);//发起异步请求
     5 }
     6 public void AsyncCallbackImpl(IAsyncResult ar){
     7     var request=ar.AsyncState as HttpWebRequest;
     8     var response=request.EndGetResponse(ar);//结束异步请求
     9     using(var stream=response.GetResponseStream()){
    10         var sbuilder=new StringBuilder();
    11         sbuilder.AppendLine($"当前线程Id:{Thread.CurrentThread.ManagedThreadId}");
    12         var reader=new StreamReader(stream);
    13         sbuilder.AppendLine(reader.ReadLine());
    14         Console.WriteLine(sbuilder.ToString());
    15     }
    16 }

    2.2.  EAP

    在大多数数据库连接驱动中使用,本人在即时通信软件中使用过,以下示例仅为展示EAP编码模式

    2.2.1.  Demo:WebClient

    1 public void Test(){
    2     var wc=new WebClient();
    3     wc.DownloadStringCompleted+=wc_DownloadStringCompleted;
    4     wc.DownloadStringAsync(new Uri("http://www.test.com/test/testEAP"));
    5 }
    6 public void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e){
    7     Console.WriteLine(e.Result);
    8 }

    2.2.2.  Demo:BackgroundWorker

     1 public void Test(){
     2     var bgworker=new BackgroundWorker();
     3     bgworker.DoWork+=bgworker_DoWork;
     4     bgworker.RunWorkerCompleted+=bgworker_RunWorkerCompleted;
     5     bgworker.RunWorkerAsync(null);//参数会被传递到DoWork事件订阅者方法中,而内部实际调用了BeginInvoke()方法
     6 }
     7 public void bgworker_DoWorker(object sender,DoWorkEventArgs e){
     8     Console.WriteLine("dowork");
     9 }
    10 public void bgworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e){
    11     Console.WriteLine("dowork completed");
    12 }

    3.  模式:TAP

    3.1.  常用对象和方法

    由于微软推荐使用TAP方式编码,所以本节内容是本篇文章的重点。其实TAP主要使用了以下对象和方法实现异步编程:

    1)      Task<Result>:异步任务

    2)      Task<Result>.ContinueWith(Action):延续任务,指定任务执行完成后延续的操作

    3)      Task.Run():创建异步任务

    4)      Task.WhenAll():在所有传入的任务都完成时才返回Task

    5)      Task.WhenAny():在传入的任务其中一个完成就会返回Task

    6)      Task.Delay():异步延时等待,示例Task.Delay(2000).Wait()

    7)      Task.Yield():进入异步方法后,在await之前,如果存在耗时的同步代码,且你想让这部分代码也异步执行,那么你就可以在进入异步方法之后的第一行添加await Task.Yield()代码了,因为它会强制将当前方法转为异步执行。

    3.2.  关键字:async/await

    1)      使用async关键字标记的方法成为异步方法,异步方法通常包含await关键字的一个或多个实例,如果异步方法中未使用await关键字标识对象方法,那么异步方法会视为同步方法。

    2)      await关键字无法等待具有void返回类型的异步方法,并且void返回方法的调用方捕获不到异步方法抛出的任何异常。

    3)      异步方法无法声明in、ref或out参数,但可以调用包含此类参数的方法。

    3.3.  使用示例

    3.3.1.  同步方法

     1 public void Test(){
     2     Console.WriteLine($"头部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
     3     var result = SayHi("abc");
     4     Console.WriteLine(result);
     5     Console.WriteLine($"尾部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
     6     Console.ReadKey();
     7 }
     8 public string SayHi(string name){
     9     Task.Delay(2000).Wait();//异步等待2s
    10     Console.WriteLine($"SayHi执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    11     return $"Hello,{name}";
    12 }

    3.3.2.  异步实现

     1 public void Test(){
     2     Console.WriteLine($"头部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
     3     var result = SayHiAsync("abc").Result;
     4     Console.WriteLine(result);
     5     Console.WriteLine($"尾部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
     6     Console.ReadKey();
     7 }
     8 public Task<string> SayHiAsync(string name){
     9     return Task.Run<string>(() => { return SayHi(name); });
    10 }
    11 public string SayHi(string name){
    12     Task.Delay(2000).Wait();//异步等待2s
    13     Console.WriteLine($"SayHi执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    14     return $"Hello,{name}";
    15 }

    3.3.3.  延续任务

     1 public void Test(){
     2     Console.WriteLine($"头部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
     3     var task = SayHiAsync("abc");
     4     task.ContinueWith(t=>{
     5         Console.WriteLine($"延续执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
     6         var result=t.Result;
     7         Console.WriteLine(result);
     8     });
     9     Console.WriteLine($"尾部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    10         Console.ReadKey();
    11 }
    12 public Task<string> SayHiAsync(string name){
    13     return Task.Run<string>(() => { return SayHi(name); });
    14 }
    15 public string SayHi(string name){
    16     Task.Delay(2000).Wait();//异步等待2s
    17     Console.WriteLine($"SayHi执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    18     return $"Hello,{name}";
    19 }

    3.3.4.  async/await重构

     1 public void Test(){
     2     Console.WriteLine($"头部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
     3     SayHiKeyPair("abc");
     4     Console.WriteLine($"尾部已执行,当前主线程Id为:{Thread.CurrentThread.ManagedThreadId}");
     5     Console.ReadKey();
     6 }
     7 public async void SayHiKeyPair(string name){
     8     Console.WriteLine($"异步调用头部执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
     9     var result = await SayHiAsync(name);
    10     Console.WriteLine($"异步调用尾部执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    11     Console.WriteLine(result);
    12 }
    13 public Task<string> SayHiAsync(string name){
    14     return Task.Run<string>(() => { return SayHi(name); });
    15 }
    16 public string SayHi(string name){
    17     Task.Delay(2000).Wait();//异步等待2s
    18     Console.WriteLine($"SayHi执行,当前线程Id为:{Thread.CurrentThread.ManagedThreadId}");
    19     return $"Hello,{name}";
    20 }

    3.4.  运行流程

    为了避免繁杂的概念,简单明了的概述为:XXXXAsync方法返回一个Task<Result>,await Task<Result>处等待异步结果,在它们中间可以执行一些与异步任务无关的逻辑。

    4.  转为:TAP

    4.1.  APM转化为TAP

    现在将第二节中的APM实现转为TAP实现,主要借助Task.Factory.FromAsync方法

     1 public void APMtoTAP(){
     2     var urlStr="http://www.test.com/test/testAPM";
     3     var request=HttpWebRequest.Create(url);
     4         Task.Factory.FromAsync<HttpWebResponse>(request.BeginGetResponse,request.EndGetResponse,null,TaskCreationOptions.None)
     5             .ContinueWith(t=>{
     6                 var response=null;
     7                 try{
     8                     response=t.Result;
     9                     using(var stream=response.GetResponseStream()){
    10                         var sbuilder=new StringBuilder();
    11                         sbuilder.AppendLine($"当前线程Id:{Thread.CurrentThread.ManagedThreadId}");
    12                         var reader=new StreamReader(stream);
    13                         sbuilder.AppendLine(reader.ReadLine());
    14                         Console.WriteLine(sbuilder.ToString());
    15                     }
    16                 }catch(AggregateException ex){
    17                     if (ex.GetBaseException() is WebException){
    18                         Console.WriteLine($"异常发生,异常信息为:{ex.GetBaseException().Message}");
    19                     }else{
    20                         throw;
    21                     }
    22                 }finally{
    23                     if(response!=null){
    24                         response.Close();
    25                     }
    26                 }
    27             });
    28 }

    4.2.  EAP转化为TAP

     1 public void Test(){
     2     var wc=new WebClient()// WebClient类支持基于事件的异步模式(EAP)
     3     var tcs = new TaskCompletionSource<string>();//创建TaskCompletionSource和它底层的Task对象
     4 
     5     wc.DownloadStringCompleted+=(sender,e)=>{//一个string下载好之后,WebClient对象会应发DownloadStringCompleted事件
     6         if(e.Error != null){
     7             tcs.TrySetException(e.Error);//试图将基础Tasks.Task<TResult>转换为Tasks.TaskStatus.Faulted状态
     8         }else if(e.Cancelled){
     9             tcs.TrySetCanceled();//试图将基础Tasks.Task<TResult>转换为Tasks.TaskStatus.Canceled状态
    10         }else{
    11             tcs.TrySetResult(e.Result);//试图将基础Tasks.Task<TResult>转换为TaskStatus.RanToCompletion状态。
    12         }
    13 };
    14     tsc.Task.ContinueWith(t=>{//为了让下面的任务在GUI线程上执行,必须标记为TaskContinuationOptions.ExecuteSynchronously
    15         if(t.IsCanceled){
    16             Console.WriteLine("操作已被取消");
    17         }else if(t.IsFaulted){
    18             Console.WriteLine("异常发生,异常信息为:" + t.Exception.GetBaseException().Message);
    19         }else{
    20             Console.WriteLine(String.Format("操作已完成,结果为:{0}", t.Result));
    21         }
    22     },TaskContinuationOptions.ExecuteSynchronously);
    23         
    24     wc.DownloadStringAsync(new Uri("http://www.test.com/test/testEAP"));
    25 }

    5.  总结

    在设计异步编程时,要确定异步操作是I/O-Bound(因I/O阻塞,又称为I/O密集型),还是CPU-Bound(因CPU阻塞,又称为计算密集型),从而更好的选择方式方法。计算密集型并不是任务越多越好,如果任务数量超过CPU的核心数,那么花费在任务切换上的时间就越多,CPU的执行效率就越低。I/O密集型由于任务主要在硬盘读写和网络读写上,所以CPU就可以处理非常多的任务。

    之所以有这篇文章,因为没有搜到类似本文,仅需一篇文章记录尽量全面的文章,所以就做了回搬运工,整理汇总一下。

    6.  参考信息

    1. https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/#:~:text=For%20more%20information%2C%20see%20Task-based%20Asynchronous%20Pattern%20%28TAP%29.,event%20handler%20delegate%20types%2C%20and%20EventArg%20-derived%20types.
    2. https://www.cnblogs.com/fanfan-90/p/12006157.html
    3. https://www.cnblogs.com/zhili/archive/2013/05/13/TAP.html
    4. https://www.cnblogs.com/jonins/p/9558275.html
  • 相关阅读:
    有点感叹,陪伴一年多的py2终于换py3了
    一句话检测XSS
    Mongodb3.4异常无法启动的处理 Process: 6874 ExecStart=/usr/bin/mongod --config /etc/mongod.conf (code=exited, status=100)
    Hadoop完全云计算平台搭建
    MySQL ERROR 3009 (HY000): Column count of mysql.user is wrong. Expected 45, found 42. Created with MySQL 50560, now running 50729. Please use mysql_upgrade to fix this error.
    v-on(事件处理)
    javascript获取以及设置光标位置
    小程序图片处理
    vue api
    处理回车提交、ctrl+enter和shift+enter都不提交->textarea正常换行
  • 原文地址:https://www.cnblogs.com/Jkinbor/p/15782947.html
Copyright © 2011-2022 走看看