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
  • 相关阅读:
    算法竞赛入门经典习题2-3 韩信点兵
    ios入门之c语言篇——基本函数——5——素数判断
    ios入门之c语言篇——基本函数——4——数值交换函数
    144. Binary Tree Preorder Traversal
    143. Reorder List
    142. Linked List Cycle II
    139. Word Break
    138. Copy List with Random Pointer
    137. Single Number II
    135. Candy
  • 原文地址:https://www.cnblogs.com/Jkinbor/p/15782947.html
Copyright © 2011-2022 走看看