zoukankan      html  css  js  c++  java
  • C#异步模式

    C#提供了几种针对异步代码编程的模式,我们一个一个看一下。

    APM

    APM即异步编程模型的简写(Asynchronous Programming Model),.NET 1.0 就开始提供的异步编程方式。

    针对一些需要异步编程的同步方法,会同时提供BeginXXX和EndXXX的异步编程搭配方法,比如Socket的Connect方法是同步方法,对应的异步编程方法就是“BeginConnect”和“EndConnect”。

    我们直接看看官方提供的同步和异步方法的声明:

    1 // 同步方法,阻塞当前线程,连接成功后继续运行
    2 public void Connect(EndPoint remoteEP)
    3 // 异步方法,开新线程运行代码,不会阻塞当前线程
    4 public IAsyncResult BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state)
    5 public void EndConnect(IAsyncResult asyncResult)

    我们可以发现异步方法除了返回值变成了 IAsyncResult 对象外,还会额外增加 AsyncCallback 类型的 callback 参数,和 object 类型的 state 参数,下面我们说说这些对象的作用:

    BeginXXX

    开始一个异步操作,不会阻塞当前线程的执行,返回的IAsyncResult可以标识当前的异步操作对象。

    EndXXX

    需要传入BeginXXX返回的IAsyncResult对象,在对应的BeginXXX异步操作还没有结束时,调用该方法后会立即阻塞当前的线程的执行,并等待异步完成后,返回异步操作的结果并让线程继续执行。

    注意,Socket的同步方法Connect没有返回值,所以EndConnect也没有返回值;我们看看FileStream的Read方法有一个int返回值(读入缓冲区中的总字节数。),当使用异步方法调用时,要获得这个返回值就可以通过调用EndRead方法得到了。

    IAsyncResult

    标记当前异步操作的唯一标识,提供当前异步操作是否完成的标志、而AsyncState属性,就是调用BeginXXX时,传入的最后一个对象。

    AsyncCallback

    本质是一个委托,我们看下其定义:

    public delegate void AsyncCallback(IAsyncResult ar);

    在异步执行完成后。会调用这个委托方法告知到主线程异步完成。

    异步APM实现

    除了使用官方提供的BeginXXX和EndXXX之外,我们该如何自己实现类似的异步方法呢?请往下看:

    委托的异步APM

    .NET除了对大量的API提供了BeginXXX和EndXXX的APM异步编程模型实现,还会对所有的委托都提供 BeginInvoke 和 EndInvoke 的方法,如同API中提供的 BeginXXX 和 EndXXX 一致,我们可以通过这个特性,快速的编写异步代码,如下:

     1 using System;
     2 using System.Diagnostics;
     3 using System.Threading;
     4 
     5 namespace NewStudyTest
     6 {
     7     public class APMDelegateTest
     8     {
     9         public APMDelegateTest()
    10         {
    11             Func<int, int> WaitSecond = time =>
    12             {
    13                 Console.Out.WriteLine("开始执行异步操作");
    14 
    15                 Thread.Sleep(time);
    16 
    17                 Console.Out.WriteLine("异步操作执行完成");
    18 
    19                 return new Random().Next();
    20             };
    21 
    22             WaitSecond.BeginInvoke(3000, ar =>
    23             {
    24                 Console.Out.WriteLine("返回值: " + WaitSecond.EndInvoke(ar) + ", " + ar.AsyncState);
    25             }, "hello APM");
    26 
    27             Console.Out.WriteLine("程序继续执行");
    28 
    29             // 因为 BeginInvoke 启动的是后台线程,所以这里要避免程序主线程关闭
    30             Thread.Sleep(5000);
    31         }
    32     }
    33 }

    注意:BeginInvoke方法是从ThreadPool中取出一个线程来执行这个方法。

    实现自己的BeginXXX和EndXXX

    其实我们运用上面提到的委托的 BeginInvoke 和 EndInvoke 方法就可以实现自己的异步方法,如下:

     1 using System;
     2 using System.Threading;
     3 
     4 namespace NewStudyTest
     5 {
     6     public class APMTest
     7     {
     8         public static void Test()
     9         {
    10             var test = new APMTest();
    11             test.BeginWaitSecond(3000, ar =>
    12             {
    13                 Console.Out.WriteLine("返回值: " + test.EndWaitSecond(ar) + ", " + ar.AsyncState);
    14             }, "hello APM");
    15 
    16             Console.Out.WriteLine("程序继续执行");
    17 
    18             // 因为 BeginInvoke 启动的是后台线程,所以这里要避免程序主线程关闭
    19             Thread.Sleep(5000);
    20         }
    21 
    22         public Func<int, int> _waitSecond;
    23 
    24         public APMTest()
    25         {
    26             _waitSecond = WaitSecond;
    27         }
    28 
    29         public int WaitSecond(int time)
    30         {
    31             Thread.Sleep(time);
    32             return new Random().Next();
    33         }
    34 
    35         public IAsyncResult BeginWaitSecond(int time, AsyncCallback callback, object state)
    36         {
    37             return _waitSecond.BeginInvoke(time, callback, state);
    38         }
    39 
    40         public int EndWaitSecond(IAsyncResult asyncResult)
    41         {
    42             return _waitSecond.EndInvoke(asyncResult);
    43         }
    44     }
    45 }

    直接调用委托对应的方法即可,是不是非常简单。

    EAP

    EAP 是 Event-based Asynchronous Pattern(基于事件的异步模型)的简写,在APM中不支持对异步操作的取消,也没有提供对进度报告的功能,所以在 .NET 2.0 中增加了 EAP 来处理这些问题。

    基于EPA的类将具有一个或者多个以Async为后缀的方法和对应的Completed等相应的事件,并且这些类都支持异步方法的取消、进度报告和报告结果。

    当我们调用实现基于事件的异步模式的类的 XxxAsync方法时,即代表开始了一个异步操作,该方法调用完之后会使一个线程池线程去执行耗时的操作。并且基于事件的异步模式是建立了APM的基础之上的。

    下面我们看一个例子:

     1 using System;
     2 using System.Net;
     3 
     4 namespace NewStudyTest
     5 {
     6     public class EAPTest
     7     {
     8         public EAPTest()
     9         {
    10             WebClient wc = new WebClient();
    11             wc.DownloadStringCompleted += DownloadStringCompleted;
    12             wc.DownloadStringAsync(new Uri("http://www.baidu.com"));
    13             Console.ReadKey();
    14         }
    15 
    16         private static void DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    17         {
    18             Console.WriteLine("网页:" + e.Result);
    19         }
    20     }
    21 }

    使用上是不是更加的简洁和直观了。

    实现自己的XXXAsync

      1 using System;
      2 using System.ComponentModel;
      3 using System.Runtime.Remoting.Messaging;
      4 using System.Threading;
      5 
      6 namespace NewStudyTest
      7 {
      8     /// <summary>
      9     /// 扩展 AsyncCompletedEventArgs 添加内容属性
     10     /// </summary>
     11     public class DownloadCompletedEventArgs : AsyncCompletedEventArgs
     12     {
     13         private string _content;
     14 
     15         public DownloadCompletedEventArgs(string content, Exception error, bool cancelled, Object userState) : base(error, cancelled, userState)
     16         {
     17             _content = content;
     18         }
     19 
     20         public string Content
     21         {
     22             get { return _content; }
     23         }
     24     }
     25 
     26     public class EAPDownloader
     27     {
     28         public static void Test()
     29         {
     30             var downloader = new EAPDownloader();
     31             downloader.progressChanged += (sender, args) =>
     32             {
     33                 Console.Out.WriteLine("下载中: " + args.ProgressPercentage);
     34             };
     35             downloader.downloadCompleted += (sender, args) =>
     36             {
     37                 Console.Out.WriteLine("下载完成:" + args.Content + ",是否取消:" + args.Cancelled);
     38             };
     39             downloader.DownloadAsync("http://xiazai.com/xxx.avi", "Hello");
     40 
     41             Thread.Sleep(5000);
     42             downloader.CancelAsync();
     43 
     44             Console.Out.WriteLine("程序继续执行");
     45             Console.ReadKey();
     46         }
     47 
     48         // 下载完成委托
     49         public delegate void DownloadCompletedEventHandler(object sender, DownloadCompletedEventArgs e);
     50 
     51         // 对外的事件
     52         public event ProgressChangedEventHandler progressChanged;
     53         public event DownloadCompletedEventHandler downloadCompleted;
     54 
     55         // AsyncOperation 使用的委托
     56         private SendOrPostCallback _onProgressChangedDelegate;
     57         private SendOrPostCallback _onDownloadCompletedDelegate;
     58 
     59         // 实际上实现下载模拟的代码委托, 即耗时函数
     60         private delegate string DownLoadHandler(string url, string name, AsyncOperation asyncOp);
     61 
     62         // 记录是否调用了取消异步方法
     63         private bool _cancelled = false;
     64 
     65         public EAPDownloader()
     66         {
     67             _onProgressChangedDelegate = new SendOrPostCallback(onProgressChanged);
     68             _onDownloadCompletedDelegate = new SendOrPostCallback(onDownloadComplete);
     69         }
     70 
     71         private void onProgressChanged(object state)
     72         {
     73             if (progressChanged != null)
     74             {
     75                 ProgressChangedEventArgs e = state as ProgressChangedEventArgs;
     76                 progressChanged(this, e);
     77             }
     78         }
     79 
     80         private void onDownloadComplete(object state)
     81         {
     82             if (downloadCompleted != null)
     83             {
     84                 DownloadCompletedEventArgs e = state as DownloadCompletedEventArgs;
     85                 downloadCompleted(this, e);
     86             }
     87         }
     88 
     89         public string DownLoad(string url, string name)
     90         {
     91             return RealDownLoad(url, name, null);
     92         }
     93 
     94         public void DownloadAsync(string url, string name)
     95         {
     96             // 异步操作的唯一标识对象
     97             AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(null);
     98 
     99             // 这里异步的实现本质上还是使用 APM 的方式
    100             DownLoadHandler dh = new DownLoadHandler(RealDownLoad);
    101             dh.BeginInvoke(url, name, asyncOp, new AsyncCallback(DownloadCallBack), asyncOp);
    102         }
    103 
    104         private void DownloadCallBack(IAsyncResult iar)
    105         {
    106             AsyncResult aresult = (AsyncResult)iar;
    107             DownLoadHandler dh = aresult.AsyncDelegate as DownLoadHandler;
    108             // 获取返回值
    109             string r = dh.EndInvoke(iar);
    110             AsyncOperation ao = iar.AsyncState as AsyncOperation;
    111             // 抛出完成事件
    112             ao.PostOperationCompleted(_onDownloadCompletedDelegate, new DownloadCompletedEventArgs(r, null, _cancelled, null));
    113         }
    114 
    115         private string RealDownLoad(string url, string name, AsyncOperation asyncOp)
    116         {
    117             _cancelled = false;
    118             for (int i = 0; i < 10; i++)
    119             {
    120                 int p = i * 10;
    121                 Console.Out.WriteLine("执行线程:" + Thread.CurrentThread.ManagedThreadId + ",传输进度:" + p + "%");
    122                 Thread.Sleep(1000);
    123                 // 取消异步操作
    124                 if (_cancelled)
    125                 {
    126                     return name + "文件下载取消!";
    127                 }
    128                 // 不为空则是异步
    129                 if (asyncOp != null)
    130                 {
    131                     // 抛出进度事件
    132                     asyncOp.Post(_onProgressChangedDelegate, new ProgressChangedEventArgs(p, null));
    133                 }
    134             }
    135             return name + "文件下载完成!";
    136         }
    137 
    138         public void CancelAsync()
    139         {
    140             _cancelled = true;
    141         }
    142     }
    143 }

    例子中,我们模拟了下载的异步操作,并实现了下载进度的通知和取消异步的操作。

    实际上,我们发现,EAP的异步实现仍然使用的还是APM的委托来实现的

    AsyncOperation

    标记一个唯一的异步操作,可以简单的理解为异步操作的唯一ID,提供的Post和PostOperationCompleted方法可以向调用的线程发送指定的消息,其用的PostMessage发送到线程,具体发送到那个线程,要看你同步上下文是和那个线程相关的。

    接收消息的地方统一使用SendOrPostCallback委托来接收即可,接收到消息再调用对应的事件即可完成事件的异步调用了。

    BackgroundWorker

    上面我们发现实现一个EAP的异步模型还是需要编写比较多的代码的,为了简化编写的代码量,微软为我们提供了名为BackgroundWorker的类来使用。

    一些耗时较长的CPU密集型运算需要开新线程执行时,就可以方便的用该类实现EAP的异步模型。

     1 using System;
     2 using System.ComponentModel;
     3 using System.Threading;
     4 
     5 namespace NewStudyTest
     6 {
     7     public class BackgroundWorkerTest
     8     {
     9         public static void Test()
    10         {
    11             BackgroundWorker backgroundWorker = new BackgroundWorker();
    12 
    13             // 支持报告进度事件
    14             backgroundWorker.WorkerReportsProgress = true;
    15             // 支持异步取消
    16             backgroundWorker.WorkerSupportsCancellation = true;
    17 
    18             backgroundWorker.ProgressChanged += (sender, args) =>
    19             {
    20                 Console.Out.WriteLine("执行中:" + args.ProgressPercentage + ", " + args.UserState);
    21             };
    22             backgroundWorker.RunWorkerCompleted += (sender, args) =>
    23             {
    24                 if (args.Cancelled)
    25                 {
    26                     Console.Out.WriteLine("执行取消!");
    27                 }
    28                 else
    29                 {
    30                     Console.Out.WriteLine("执行完毕:" + args.Result);
    31                 }
    32             };
    33             backgroundWorker.DoWork += (sender, args) =>
    34             {
    35                 BackgroundWorker bw = sender as BackgroundWorker;
    36 
    37                 // 获取参数
    38                 int count = (int)args.Argument;
    39 
    40                 int sum = 0;
    41                 for (int i = 0; i < count; i++)
    42                 {
    43                     sum++;
    44 
    45                     // 模拟耗时操作
    46                     Thread.Sleep(1000);
    47 
    48                     if (bw.CancellationPending)
    49                     {
    50                         args.Cancel = true;
    51                         return;
    52                     }
    53 
    54                     // 通知进度
    55                     bw.ReportProgress(i, "msg " + i);
    56                 }
    57 
    58                 args.Result = sum;
    59             };
    60             // 开始执行,可传参数
    61             backgroundWorker.RunWorkerAsync(10);
    62 
    63             Thread.Sleep(5000);
    64             backgroundWorker.CancelAsync();
    65 
    66             Console.Out.WriteLine("程序继续执行");
    67             Console.ReadKey();
    68         }
    69     }
    70 }

    可以发现通过使用BackgroundWork类可以省去自己实现EAP的繁琐步骤,可以专注于异步的实现。

    TAP

    进入.NET4.0的时代,微软提出了基于任务的异步模式(Task-based Asynchronous Pattern),该模式主要使用同样在.NET4.0中引入的任务(即 System.Threading.Tasks.Task 和 Task<T> 类)来完成异步编程。

    我们怎样区分.NET类库中的类实现了基于任务的异步模式呢? 这个识别方法很简单,当看到类中存在TaskAsync为后缀的方法时就代表该类实现了TAP

    我们先直接看例子:

     1 using System;
     2 using System.Threading;
     3 using System.Threading.Tasks;
     4 
     5 namespace NewStudyTest
     6 {
     7     public class TAPTest
     8     {
     9         public TAPTest()
    10         {
    11             CancellationTokenSource cts = new CancellationTokenSource();
    12 
    13             Task task = new Task((() =>
    14             {
    15                 int result = DoCacl(10, cts.Token, new Progress<int>((i =>
    16                 {
    17                     Console.Out.WriteLine("执行中:" + i);
    18                 })));
    19                 Console.Out.WriteLine("执行完毕:" + result);
    20             }));
    21             task.Start();
    22 
    23             Thread.Sleep(5000);
    24             cts.Cancel();
    25 
    26             Console.Out.WriteLine("程序继续执行");
    27             Console.ReadKey();
    28         }
    29 
    30         public int DoCacl(int count, CancellationToken ct, IProgress<int> progress)
    31         {
    32             int sum = 0;
    33 
    34             for (int i = 0; i < count; i++)
    35             {
    36                 sum++;
    37 
    38                 // 模拟耗时操作
    39                 Thread.Sleep(1000);
    40 
    41                 // 取消异步的判断
    42                 if (ct.IsCancellationRequested)
    43                 {
    44                     return -1;
    45                 }
    46 
    47                 // 进度报告
    48                 progress.Report(i);
    49             }
    50 
    51             return sum;
    52         }
    53     }
    54 }

    我们发现通过使用Task类,异步代码的编写变得更加的简洁和易于理解,下面我们说说用到的一些类型。

    CancellationTokenSource

    支持取消异步操作的类。

    CancellationToken

    标识唯一的异步进程,当CancellationTokenSource调用取消后,可以通过判断其IsCancellationRequested来确定是否要退出耗时操作。

    IProgress

    向外部通知进度消息的接口。

    异步方法直接返回值的例子

     1 using System;
     2 using System.Threading;
     3 using System.Threading.Tasks;
     4 
     5 namespace NewStudyTest
     6 {
     7     public class TAPTest
     8     {
     9         public TAPTest()
    10         {
    11             Task<int> task = new Task<int>((() =>
    12             {
    13                 Thread.Sleep(3000);
    14 
    15                 return 123;
    16             }));
    17             task.Start();
    18 
    19             Console.Out.WriteLine("程序继续执行");
    20 
    21             // 阻塞线程等待 Task 执行完毕
    22             task.Wait();
    23 
    24             Console.Out.WriteLine("异步结果:" + task.Result);
    25 
    26             Console.ReadKey();
    27         }
    28     }
    29 }

    语法糖await和async

    为了更方便的编写异步代码,.NET4.5(C#5.0)中,提供了语法糖await和async来实现用同步的方式来编写异步代码,注意需要和Task配合。

    我们先看一个没有用该语法糖的例子:

     1 using System;
     2 using System.Threading;
     3 using System.Threading.Tasks;
     4 
     5 namespace NewStudyTest
     6 {
     7     public class TaskTest
     8     {
     9         public TaskTest()
    10         {
    11             Task.Run((() =>
    12             {
    13                 Task task1 = Execute1();
    14                 task1.Wait();
    15                 Task<int> task2 = Execute2();
    16                 task2.Wait();
    17                 Console.Out.WriteLine("返回值: " + task2.Result);
    18                 Task task3 = Execute3();
    19                 task3.Wait();
    20 
    21                 Console.Out.WriteLine("执行完毕");
    22             }));
    23 
    24             Console.Out.WriteLine("程序继续执行");
    25             Console.ReadKey();
    26         }
    27 
    28         private Task Execute1()
    29         {
    30             Task task = new Task((() =>
    31             {
    32                 Thread.Sleep(1000);
    33                 Console.Out.WriteLine("耗时操作1");
    34             }));
    35             task.Start();
    36             return task;
    37         }
    38 
    39         private Task<int> Execute2()
    40         {
    41             Task<int> task = new Task<int>((() =>
    42             {
    43                 Thread.Sleep(1000);
    44                 Console.Out.WriteLine("耗时操作2");
    45                 return 123;
    46             }));
    47             task.Start();
    48             return task;
    49         }
    50 
    51         private Task Execute3()
    52         {
    53             Task task = new Task((() =>
    54             {
    55                 Thread.Sleep(1000);
    56                 Console.Out.WriteLine("耗时操作3");
    57             }));
    58             task.Start();
    59             return task;
    60         }
    61     }
    62 }

    再看使用了await和async语法糖的例子:

     1 using System;
     2 using System.Threading;
     3 using System.Threading.Tasks;
     4 
     5 namespace NewStudyTest
     6 {
     7     public class Task2Test
     8     {
     9         public Task2Test()
    10         {
    11             Task.Run(async () =>
    12             {
    13                 await Execute1();
    14                 Console.Out.WriteLine("返回值: " + await Execute2());
    15                 await Execute3();
    16 
    17                 Console.Out.WriteLine("执行完毕");
    18             });
    19 
    20             Console.Out.WriteLine("程序继续执行");
    21             Console.ReadKey();
    22         }
    23 
    24         private async Task Execute1()
    25         {
    26             Thread.Sleep(1000);
    27             Console.Out.WriteLine("耗时操作1");
    28         }
    29 
    30         private async Task<int> Execute2()
    31         {
    32             Thread.Sleep(1000);
    33             Console.Out.WriteLine("耗时操作2");
    34             return 123;
    35         }
    36 
    37         private async Task Execute3()
    38         {
    39             Thread.Sleep(1000);
    40             Console.Out.WriteLine("耗时操作3");
    41         }
    42     }
    43 }

    实现的效果完全一致,但是使用await和async可以更简洁也更像同步代码易于理解。

    需要注意的地方

    标记为 async 的异步函数的返回类型只能为: Task、Task<TResult>。

    • Task<TResult>: 代表一个返回值T类型的操作。
    • Task: 代表一个无返回值的操作。

    await 只能修饰被调用的 async 的方法。

    天道酬勤,功不唐捐!
  • 相关阅读:
    jackson 枚举 enum json 解析类型 返回数字 或者自定义文字 How To Serialize Enums as JSON Objects with Jackson
    Antd Pro V5 中ProTable 自定义查询参数和返回值
    ES6/Antd 代码阅读记录
    es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?
    Antd Hooks
    使用.Net Core开发WPF App系列教程(其它 、保存控件内容为图片)
    使用.Net Core开发WPF App系列教程( 三、与.Net Framework的区别)
    使用.Net Core开发WPF App系列教程( 四、WPF中的XAML)
    使用.Net Core开发WPF App系列教程( 二、在Visual Studio 2019中创建.Net Core WPF工程)
    使用.Net Core开发WPF App系列教程( 一、.Net Core和WPF介绍)
  • 原文地址:https://www.cnblogs.com/hammerc/p/14389064.html
Copyright © 2011-2022 走看看