.NET异步编程模式的三种类型介绍

一、引言

.NET中很多的类、接口在设计的时候都考虑了多线程问题,简化了多线程程序的开发,不用自己去写WaitHandler等这些底层的代码,由于历史的发展,这些类的接口设计有着三种不同的风格:EAP、APM和TPL。目前重点用TPL。

二、EAP

EAP是Event-based Asynchronous Pattem(基于事件的异步模型)的简写,类似于Ajax中的XmlHttpRequest,send之后并不是处理完成了,而是在onreadystatechange事件中再通知处理完成。看下面的一个示例。

我们创建一个Winform程序,窗体上面有一个按钮和一个文本框,点击按钮开始下载,下载完成以后给文本框赋值,界面如图所示:

开始下载代码如下:

using System;
using System.Net;
using System.Windows.Forms;

namespace EAPDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 开始下载
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDownload_Click(object sender, EventArgs e)
        {
            // 实例化对象
            WebClient wc = new WebClient();
            // 注册完成事件
            wc.DownloadStringCompleted += Wc_DownloadStringCompleted;
            // 异步下载 不会阻塞界面,窗体可以随意拖动
            wc.DownloadStringAsync(new Uri("http://www.baidu.com"));
        }

        /// <summary>
        /// 下载完成事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // 给文本框赋值
            txtContent.Text = e.Result;
        }
    }
}

程序执行的时候,界面可以随意拖动,不会卡住界面,而使用同步方法就会卡住界面,使用同步方法时放到一个单独的线程里面也可以实现不卡住界面,但是那种写法比较复杂,直接使用异步方法就可以完成。

EAP的优点是简单,缺点是当实现复杂的业务的时候很麻烦,比如下载A成功后再下载B,如果下载B成功再下载C,否则就下载D。

EAP的类的特点是:一个异步方法配合一个***Completed事件。.NET中基于EAP的类比较少,也有更好的替代品,因此了解即可。

三、APM

APM(Asynchronous Programming Model)的缩写,是.NET旧版本中广泛使用的异步编程模型。使用了APM的异步方法会返回一个IAsyncResult 对象,这个对象有一个重要的属性:AsyncWaitHandle。它是一个用来等待异步任务执行结束的一个同步信号。看下面的一个示例。

界面上由一个按钮和一个文本框,点击按钮开始读取文件内容,读取完毕之后给文本框赋值,界面如下图所示:

按钮代码如下:

using System;
using System.IO;
using System.Text;
using System.Windows.Forms;

namespace APMDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnRead_Click(object sender, EventArgs e)
        {
            // 打开文件
            FileStream fs = File.OpenRead("F:/test.txt");
            // 声明数组
            byte[] buffer = new byte[1024];
            // BeginRead开始读取
            IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);
            //等待任务执行结束
            aResult.AsyncWaitHandle.WaitOne();
            this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            // 结束读取
            fs.EndRead(aResult);
        }
    }
}

因为里面使用了WaitOne(),这里会产生等待,如果等待时候过长,还是会产生界面卡顿,所以最好还是放到多线程里面去执行,修改后的代码如下:

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace APMDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnRead_Click(object sender, EventArgs e)
        {
            //// 打开文件
            //FileStream fs = File.OpenRead("F:/test.txt");
            //// 声明数组
            //byte[] buffer = new byte[1024];
            //// BeginRead开始读取
            //IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);
            ////等待任务执行结束
            //aResult.AsyncWaitHandle.WaitOne();
            //this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            //// 结束读取
            //fs.EndRead(aResult);

            #region 多线程
            ThreadPool.QueueUserWorkItem(state =>
            {
                // 打开文件
                FileStream fs = File.OpenRead("F:/test.txt");
                // 声明数组
                byte[] buffer = new byte[1024];
                // BeginRead开始读取
                IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);
                //等待任务执行结束
                aResult.AsyncWaitHandle.WaitOne();
                this.txtContent.BeginInvoke(new Action(() =>
                {
                    this.txtContent.Text= Encoding.UTF8.GetString(buffer); ;
                }));
                // 结束读取
                fs.EndRead(aResult);
            });
            #endregion
        }
    }
}

如果不加 aResult.AsyncWaitHandle.WaitOne() 那么很有可能打印出空白,因为 BeginRead只是“开始读取”。调用完成一般要调用 EndXXX 来回收资源。

APM 的特点是:方法名字以 BeginXXX 开头,返回类型为 IAsyncResult,调用结束后需要EndXXX。

.Net 中有如下的常用类支持 APM:Stream、SqlCommand、Socket 等。

APM 还是太复杂,了解即可。

四、TPL

TPL(Task Parallel Library)是.NET 4.0之后带来的新特性,更简洁,更方便。现在在.NET平台下已经被广泛的使用。我们看下面的一个示例。

界面有一个开始按钮和一个文本框,点击按钮开始读取文件内容,读取完毕赋值到文本框中,开始按钮代码如下:

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TPLDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private  void btnRead_Click(object sender, EventArgs e)
        {
            // 打开文件
            using (FileStream fs = File.OpenRead("F:/test.txt"))
            {
                // 声明数组
                byte[] buffer = new byte[1024];
                // BeginRead开始读取
                Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
                // 等待
                task.Wait();
                this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            }
        }
    }
}

执行task.Wait()的时候如果比较耗时,也会造成界面卡顿,所以最好还是放到一个单独的线程中取执行,优化后的代码如下:

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TPLDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private  void btnRead_Click(object sender, EventArgs e)
        {
            //// 打开文件
            //using (FileStream fs = File.OpenRead("F:/test.txt"))
            //{
            //    // 声明数组
            //    byte[] buffer = new byte[1024];
            //    // BeginRead开始读取
            //    Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
            //    // 等待
            //    task.Wait();
            //    this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            //}

            // 开启一个线程
            ThreadPool.QueueUserWorkItem(state =>
            {
                // 打开文件
                using (FileStream fs = File.OpenRead("F:/test.txt"))
                {
                    // 声明数组
                    byte[] buffer = new byte[1024];
                    // BeginRead开始读取
                    Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
                    // 等待
                    task.Wait();
                    this.txtContent.Invoke(new Action(() =>
                    {
                        this.txtContent.Text = Encoding.UTF8.GetString(buffer);
                    }));
                }
            });
        }
    }
}

这样用起来和APM相比的好处是:不需要EndXXX。但是TPL还有更强大的功能,那就是异步方法,我们在界面上在增加一个按钮用来演示使用异步方法,其实现代码如下:

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TPLDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private  void btnRead_Click(object sender, EventArgs e)
        {
            //// 打开文件
            //using (FileStream fs = File.OpenRead("F:/test.txt"))
            //{
            //    // 声明数组
            //    byte[] buffer = new byte[1024];
            //    // BeginRead开始读取
            //    Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
            //    // 等待
            //    task.Wait();
            //    this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            //}

            // 开启一个线程
            ThreadPool.QueueUserWorkItem(state =>
            {
                // 打开文件
                using (FileStream fs = File.OpenRead("F:/test.txt"))
                {
                    // 声明数组
                    byte[] buffer = new byte[1024];
                    // BeginRead开始读取
                    Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
                    // 等待
                    task.Wait();
                    this.txtContent.Invoke(new Action(() =>
                    {
                        this.txtContent.Text = Encoding.UTF8.GetString(buffer);
                    }));
                }
            });
        }

        /// <summary>
        /// 异步读取方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void btnAsync_Click(object sender, EventArgs e)
        {
            // 打开文件
            using (FileStream fs = File.OpenRead("F:/test.txt"))
            {
                // 声明数组
                byte[] buffer = new byte[1024];
                // BeginRead开始读取
                await fs.ReadAsync(buffer, 0, buffer.Length);
                this.txtContent.Text = Encoding.UTF8.GetString(buffer);
            }
        }
    }
}

使用异步方法,要把方法标记为异步的,就是将方法标记为async,然后方法中使用await,await表示等待ReadAsync执行结束。

注意:方法中如果使用了await,那么方法就必须标记为async,但也不是所有方法都可以被轻松的标记为async。WinForm中的事件处理方法都可以被标记为async,MVC中的Action方法也可以被标记为async,控制台的Main方法则不能被标记为async。

TPL的特点是:方法都以XXXAsync结尾,返回值类型是泛型的Task<T>。

TPL让我们可以用线性的方式去编写异步程序,不在需要像EAP中那样搞一堆回调、逻辑跳来跳去了。await现在已经被JavaScript借鉴走了!

我们用await实现下面的一个功能:先下载A,如果下载的内容长度大于100则下载B,否则下载C。代码实现如下:

using System;
using System.Net;
using System.Windows.Forms;

namespace AwaitDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void btnDownload_Click(object sender, EventArgs e)
        {
            WebClient wc = new WebClient();
            string str=await wc.DownloadStringTaskAsync("http://www.baidu.com");
            if(str.Length>100)
            {
                str = await wc.DownloadStringTaskAsync("https://www.jd.com/");
            }
            else
            {
                str = await wc.DownloadStringTaskAsync("http://www.dangdang.com/");
            }
            this.txtContent.Text = str;
        }
    }
}

Task<T>中的T是什么类型在每个方法中都不一样,具体是什么类型要看文档。

WebClient、Stream、Socket等这些“历史悠久”的类都同时提供了APM、TPL更改的API,甚至有的还提供了EAP风格的API。这里建议尽可能的使用TPL风格的。

五、如何编写异步方法

在上面的示例中,我们都是使用的.NET框架给我们提供好的API,如果我们想自己编写异步方法该怎么办?因为目前Task使用最广泛,所以我们这里以TPL为例讲解如何编写自己的异步方法。

首先异步方法的返回值要是Task<T>类型,方法里面返回一个Task类型,潜规则(不要求)是方法名字以Async结尾,这样就会知道这是一个异步方法。看下面的例子:

/// <summary>
/// 自定义异步方法,返回类型是string
/// </summary>
/// <returns></returns>
private Task<string> TestTask()
{
    return Task.Run<string>(() =>
    {
        // 让线程休眠3秒,模拟一个耗时的操作
        Thread.Sleep(3000);
        return "这是一个异步方法";
    });
}

这样就编写好了一个异步方法。那么怎么使用这个异步方法呢?使用方法跟我们使用.NET框架提供的异步方法一样,看下面调用异步方法的代码:

using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AwaitDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void btnDownload_Click(object sender, EventArgs e)
        {
            WebClient wc = new WebClient();
            string str=await wc.DownloadStringTaskAsync("http://www.baidu.com");
            if(str.Length>100)
            {
                str = await wc.DownloadStringTaskAsync("https://www.jd.com/");
            }
            else
            {
                str = await wc.DownloadStringTaskAsync("http://www.dangdang.com/");
            }
            this.txtContent.Text = str;
        }

        /// <summary>
        /// 自定义异步方法,返回类型是string
        /// </summary>
        /// <returns></returns>
        private Task<string> TestTask()
        {
            return Task.Run<string>(() =>
            {
                // 让线程休眠3秒,模拟一个耗时的操作
                Thread.Sleep(3000);
                return "这是一个异步方法";
            });
}

        /// <summary>
        /// 调用自定义的异步方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void btnCall_Click(object sender, EventArgs e)
        {
            string str = await TestTask();
            this.txtContent.Text = str;
        }
    }
}

程序输出结果:

这样就可以调用自己写的异步方法了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 高效的.Net UDP异步编程实现分析

    因为要写一个网络程序要用到UDP协议,UDP这东西比较麻烦,又不像TCP一样提供可靠的连接,发送接收的超时实在不好设计,最后只要用Timer来检测有没有想要的数据包-_#,不过这不是这次的重点,重点是怎么建立一种高效的UDP机制来实时接收服务器发送过来的数据包. CodeProject上有个例子是开个线程去同步接收,这样倒是可以满足我的程序需求,不过实际中遇到几个问题: 1.程序开销大,内存狂飙,接一次数据就要重新开一次线程 2.由于主界面和底层是完全隔离只是通过中间的接口来通讯,导致线程总是不

  • 在.NET Core中使用异步编程的方法步骤

    近期对于异步和多线程编程有些启发,所以我决定把自己的理解写下来. 思考:为什么要使用异步编程? 我们先看看同步方法和异步方法之前在程序中执行的逻辑: 1. 同步方法 static void Main(string[] args) { Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:开始"); // 调用同步方法 SyncTestMethod(); Console.WriteL

  • .NET实现异步编程async和await

    await和async是.NET Framework4.5框架.C#5.0语法里面出现的,await和async是语法糖. 注意: 1.async出现在方法的声明里面,任何一个方法都可以增加async. 2.await放在Task前面,async和await是成对出现的,只有async是没有意义的,只有await是报错的. 只有async是没有意义的. 只有await是报错的. 3.await 只能放在task前面,不推荐void返回值,使用Task来代替.Task和Task<T>能够使用aw

  • .NET异步编程总结----四种实现模式代码总结

    最近很忙,既要外出找工作又要兼顾老板公司的项目.今天在公司,忙里偷闲,总结一下.NET中的异步调用函数的实现方法,DebugLZQ在写这篇博文之前自己先动手写了本文的所有示例代码,开写之前是做过功课的,用代码说话方有说服力. 本文的内容旨在用最简洁的代码来把异步调用的方法说清楚,园子里的高手老鸟可以绕行,不喜勿喷,非诚勿扰~ lz的前一篇文章简单的说了下异步,主要是从理解上来讲:这篇文章主要写具体的实现方法.实现异步编程有4种方法可供选择,这4种访求实际上也对应着4种异步调用的模式,分为"等待&

  • .NET中的异步编程-EAP/APM使用方法及案例介绍

    从.NET 4.5开始,支持的三种异步编程模式: •基于事件的异步编程设计模式 (EAP,Event-based Asynchronous Pattern) •异步编程模型(APM,Asynchronous Programming Model) •基于任务的编程模型(TAP,Task-based Asynchronous Pattern) 基于任务的异步模式 (TAP) 是基于 System.Threading.Tasks 命名空间的 Task 和 Task<TResult>,用于表示任意异步

  • .net4.5使用async和await异步编程实例

    关于异步编程的简单理解: 在.NET4.5中新增了异步编程的新特性async和await,使得异步编程更为简单.通过特性可以将这项复杂的工作交给编译器来完成了.之前传统的方式来实现异步编程较为复杂,这样对于程序猿来说处理起来比较困难,调试也没那么方便,后续的维护工作也比较痛苦. Async和Await关键字是C#异步编程的核心.通过使用这两个关键字,你可以使用.NET Framework 或 Windows Runtime的资源创建一个异步方法如同创建一个同步方法一样容易. 接下来通过VS201

  • ASP.Net中的async+await异步编程的实现

    在.NET Framework4.5框架.C#5.0语法中,通过async和await两个关键字,引入了一种新的基于任务的异步编程模型(TAP).在这种方式下,可以通过类似同步方式编写异步代码,极大简化了异步编程模型. 用法: public async Task<int> GetDotNetCountAsync() { // Suspends GetDotNetCount() to allow the caller (the web server) // to accept another r

  • .NET异步编程模式的三种类型介绍

    一.引言 .NET中很多的类.接口在设计的时候都考虑了多线程问题,简化了多线程程序的开发,不用自己去写WaitHandler等这些底层的代码,由于历史的发展,这些类的接口设计有着三种不同的风格:EAP.APM和TPL.目前重点用TPL. 二.EAP EAP是Event-based Asynchronous Pattem(基于事件的异步模型)的简写,类似于Ajax中的XmlHttpRequest,send之后并不是处理完成了,而是在onreadystatechange事件中再通知处理完成.看下面的

  • JavaScript中实现异步编程模式的4种方法

    你可能知道,Javascript语言的执行环境是"单线程"(single thread). 所谓"单线程",就是指一次只能完成一件任务.如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推. 这种模式的好处是实现起来比较简单,执行环境相对单纯:坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行.常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他

  • .NET2.0版本中基于事件的异步编程模式(EAP)

    一.引言 APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题——不支持对异步操作的取消和没有提供对进度报告的功能,对于有界面的应用程序来说,进度报告和取消操作的支持也是必不可少的. 微软在.NET 2.0的时候就为我们提供了一个新的异步编程模型,也就是基于事件的异步编程模型——EAP(Event-based Asynchronous Pattern ). 二.介绍 实现了基于事件的异步模式的类将具有一个或者多个以Async为后缀的方法和对应的Completed事件,并且这些类

  • c# 基于任务的异步编程模式(TAP)的异常处理

    在前面讲到了<基于任务的异步编程模式(TAP)>,但是如果调用异步方法,没有等待,那么调用异步方法的线程中使用传统的try/catch块是不能捕获到异步方法中的异常.因为在异步方法执行出现异常之前,已经执行完毕. 1.没有等待的调用异步方法 ThrowAfter方法是在一定延迟后抛出一个异常: private async Task ThrowAfter(int ms,string message) { await Task.Delay(ms); Console.WriteLine("

  • c# 基于任务的异步编程模式(TAP)

    异步编程是C#5.0的一个重要改进,提供两个关键字:async和await.使用异步编程,方法的调用是在后台运行(通常在线程或任务的帮助下),但不会阻塞调用线程.异步模式分为3种:异步模式.基于事件的异步模式和基于任务的异步模式(TAP).TAP是利用关键字async和await实现的,本文将讲解TAP模式.async和await关键字只是编译器的功能.编译器最终会用Task类创建代码. 1.创建任务 建立一个同步方法Greeting,该方法在等待一段时间后,返回一个字符串. private s

  • 详解JS异步加载的三种方式

    一:同步加载 我们平时使用的最多的一种方式. <script src="http://yourdomain.com/script.js"></script> <script src="http://yourdomain.com/script.js"></script> 同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止后续的解析,只有当当前加载完成,才能进行下一步操作.所以默认同步执行才是安全的.但这样如果js中有输

  • C/C++程序开发中实现信息隐藏的三种类型

    无论是模块化设计,还是面向对象设计,还是分层设计,实现子系统内部信息的对外隐藏都是最关键的内在要求.以本人浅显的经验,把信息隐藏按照程度的不同分成(1)不可见不可用(2)可见不可用(3)可见可用. 1 不可见不可用 就是说模块内部的变量.结构体.类定义对外部而已完全隐藏,外部对此一无所知.常用的实现方法就是利用不透明指针,请参见我的博文C语言开发函数库时利用不透明指针对外隐藏结构体细节. 这种方法同样适用于C++语言,一种可能的实现方式为面向接口编程. 头文件 IMyClass.h class

  • java定义数组的三种类型总结

    三种定义数组的格式如下: int[] arr1=new int[10]; int[] arr2={1,2,3,6}; int[] arr3=new int[]{1,2,3,4,5,6,7,22}; 注意:数组的length是一个属性,而字符串的length()是一个方法了!!!虽然都是求的他们各自的长度 package 第四天; public class 数组 { public void showArray(int[] arr) { for(int i=0;i<arr.length;i++) S

  • java构造函数的三种类型总结

    我们说构造函数能处理参数的问题,但其实也要分三种情况进行讨论.目前有三种类型:无参.有参和默认.根据不同的参数情况,需要我们分别进行构造函数的讨论.这里重点是无参构造函数的初始化也要分两种方法进行分析.下面我们就这三种不同的构造函数类型分别为大家进行展示. 1.无参构造函数 不带入参的构造函数叫无参构造函数,对类的成员初始化有两种方法: (1)在类成员变量声明时进行初始化 public class MyClass { private String name = "Jerry"; pri

  • 漫画讲解C语言中最近公共祖先的三种类型

    最近公共祖先定义 查找最近公共祖先 三叉链 代码如下: //三叉链 struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode *parent; TreeNode(int x) : val(x), left(NULL), right(NULL), parent(NULL) {} }; class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* ro

随机推荐