您现在的位置是:网站首页> 编程资料编程资料
.NET异步编程模式的三种类型介绍_实用技巧_
2023-05-24
322人已围观
简介 .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(); } /// /// 开始下载 /// /// /// private void btnDownload_Click(object sender, EventArgs e) { // 实例化对象 WebClient wc = new WebClient(); // 注册完成事件 wc.DownloadStringCompleted += Wc_DownloadStringCompleted; // 异步下载 不会阻塞界面,窗体可以随意拖动 wc.DownloadStringAsync(new Uri("http://www.baidu.com")); } /// /// 下载完成事件 /// /// /// 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 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 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 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 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 task = fs.ReadAsync(buffer, 0, buffer.Length); // 等待 task.Wait(); this.txtContent.Invoke(new Action(() => { this.txtContent.Text = Encoding.UTF8.GetString(buffer); })); } }); } /// /// 异步读取方法 /// /// /// 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
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
WebClient、Stream、Socket等这些“历史悠久”的类都同时提供了APM、TPL更改的API,甚至有的还提供了EAP风格的API。这里建议尽可能的使用TPL风格的。
五、如何编写异步方法
在上面的示例中,我们都是使用的.NET框架给我们提供好的API,如果我们想自己编写异步方法该怎么办?因为目前Task使用最广泛,所以我们这里以TPL为例讲解如何编写自己的异步方法。
首先异步方法的返回值要是Task
////// 自定义异步方法,返回类型是string /// ///private Task TestTask() { return Task.Run (() => { // 让线程休眠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; } /// /// 自定义异步方法,返回类型是string /// /// private Task TestTask() { return Task.Run(() => { // 让线程休眠3秒,模拟一个耗时的操作 Thread.Sleep(3000); return "这是一个异步方法"; }); } /// /// 调用自定义的异步方法 /// /// /// private async void btnCall_Click(object sender, EventArgs e) { string str = await TestTask(); this.txtContent.Text = str; } } }提示:
本文由神整理自网络,如有侵权请联系本站删除!
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!
相关内容
- ASP.NET Core项目中调用WebService的方法_实用技巧_
- WPF实现文本描边+外发光效果的示例代码_实用技巧_
- 使用Supervisor守护ASP.NET Core应用程序进程_实用技巧_
- 部署ASP.NET Core程序到Linux系统_基础应用_
- 部署ASP.NET Core程序到Windows系统_基础应用_
- 详解如何创建一个.NET Core工程_实用技巧_
- LazyCaptcha自定义随机验证码和字体的示例详解_实用技巧_
- asp.net Core中同名服务注册的实现代码_实用技巧_
- ASP.NET MVC中_ViewStart.cshtml作用介绍_基础应用_
- ASP.NET MVC中的路由原理与用法_实用技巧_
点击排行
本栏推荐
