.NET異步編程模式的三種類型介紹
一、引言
.NET中很多的類、接口在設(shè)計的時候都考慮了多線程問題,簡化了多線程程序的開發(fā),不用自己去寫WaitHandler等這些底層的代碼,由于歷史的發(fā)展,這些類的接口設(shè)計有著三種不同的風(fēng)格:EAP、APM和TPL。目前重點用TPL。
二、EAP
EAP是Event-based Asynchronous Pattem(基于事件的異步模型)的簡寫,類似于Ajax中的XmlHttpRequest,send之后并不是處理完成了,而是在onreadystatechange事件中再通知處理完成??聪旅娴囊粋€示例。
我們創(chuàng)建一個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;
}
}
}程序執(zhí)行的時候,界面可以隨意拖動,不會卡住界面,而使用同步方法就會卡住界面,使用同步方法時放到一個單獨的線程里面也可以實現(xiàn)不卡住界面,但是那種寫法比較復(fù)雜,直接使用異步方法就可以完成。
EAP的優(yōu)點是簡單,缺點是當(dāng)實現(xiàn)復(fù)雜的業(yè)務(wù)的時候很麻煩,比如下載A成功后再下載B,如果下載B成功再下載C,否則就下載D。
EAP的類的特點是:一個異步方法配合一個***Completed事件。.NET中基于EAP的類比較少,也有更好的替代品,因此了解即可。
三、APM
APM(Asynchronous Programming Model)的縮寫,是.NET舊版本中廣泛使用的異步編程模型。使用了APM的異步方法會返回一個IAsyncResult 對象,這個對象有一個重要的屬性:AsyncWaitHandle。它是一個用來等待異步任務(wù)執(zhí)行結(jié)束的一個同步信號??聪旅娴囊粋€示例。
界面上由一個按鈕和一個文本框,點擊按鈕開始讀取文件內(nèi)容,讀取完畢之后給文本框賦值,界面如下圖所示:

按鈕代碼如下:
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");
// 聲明數(shù)組
byte[] buffer = new byte[1024];
// BeginRead開始讀取
IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);
//等待任務(wù)執(zhí)行結(jié)束
aResult.AsyncWaitHandle.WaitOne();
this.txtContent.Text = Encoding.UTF8.GetString(buffer);
// 結(jié)束讀取
fs.EndRead(aResult);
}
}
}因為里面使用了WaitOne(),這里會產(chǎn)生等待,如果等待時候過長,還是會產(chǎn)生界面卡頓,所以最好還是放到多線程里面去執(zhí)行,修改后的代碼如下:
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");
//// 聲明數(shù)組
//byte[] buffer = new byte[1024];
//// BeginRead開始讀取
//IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);
////等待任務(wù)執(zhí)行結(jié)束
//aResult.AsyncWaitHandle.WaitOne();
//this.txtContent.Text = Encoding.UTF8.GetString(buffer);
//// 結(jié)束讀取
//fs.EndRead(aResult);
#region 多線程
ThreadPool.QueueUserWorkItem(state =>
{
// 打開文件
FileStream fs = File.OpenRead("F:/test.txt");
// 聲明數(shù)組
byte[] buffer = new byte[1024];
// BeginRead開始讀取
IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);
//等待任務(wù)執(zhí)行結(jié)束
aResult.AsyncWaitHandle.WaitOne();
this.txtContent.BeginInvoke(new Action(() =>
{
this.txtContent.Text= Encoding.UTF8.GetString(buffer); ;
}));
// 結(jié)束讀取
fs.EndRead(aResult);
});
#endregion
}
}
}如果不加 aResult.AsyncWaitHandle.WaitOne() 那么很有可能打印出空白,因為 BeginRead只是“開始讀取”。調(diào)用完成一般要調(diào)用 EndXXX 來回收資源。
APM 的特點是:方法名字以 BeginXXX 開頭,返回類型為 IAsyncResult,調(diào)用結(jié)束后需要EndXXX。
.Net 中有如下的常用類支持 APM:Stream、SqlCommand、Socket 等。
APM 還是太復(fù)雜,了解即可。
四、TPL
TPL(Task Parallel Library)是.NET 4.0之后帶來的新特性,更簡潔,更方便?,F(xiàn)在在.NET平臺下已經(jīng)被廣泛的使用。我們看下面的一個示例。
界面有一個開始按鈕和一個文本框,點擊按鈕開始讀取文件內(nèi)容,讀取完畢賦值到文本框中,開始按鈕代碼如下:
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"))
{
// 聲明數(shù)組
byte[] buffer = new byte[1024];
// BeginRead開始讀取
Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
// 等待
task.Wait();
this.txtContent.Text = Encoding.UTF8.GetString(buffer);
}
}
}
}執(zhí)行task.Wait()的時候如果比較耗時,也會造成界面卡頓,所以最好還是放到一個單獨的線程中取執(zhí)行,優(yōu)化后的代碼如下:
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"))
//{
// // 聲明數(shù)組
// 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"))
{
// 聲明數(shù)組
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還有更強大的功能,那就是異步方法,我們在界面上在增加一個按鈕用來演示使用異步方法,其實現(xiàn)代碼如下:
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"))
//{
// // 聲明數(shù)組
// 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"))
{
// 聲明數(shù)組
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"))
{
// 聲明數(shù)組
byte[] buffer = new byte[1024];
// BeginRead開始讀取
await fs.ReadAsync(buffer, 0, buffer.Length);
this.txtContent.Text = Encoding.UTF8.GetString(buffer);
}
}
}
}使用異步方法,要把方法標(biāo)記為異步的,就是將方法標(biāo)記為async,然后方法中使用await,await表示等待ReadAsync執(zhí)行結(jié)束。
注意:方法中如果使用了await,那么方法就必須標(biāo)記為async,但也不是所有方法都可以被輕松的標(biāo)記為async。WinForm中的事件處理方法都可以被標(biāo)記為async,MVC中的Action方法也可以被標(biāo)記為async,控制臺的Main方法則不能被標(biāo)記為async。
TPL的特點是:方法都以XXXAsync結(jié)尾,返回值類型是泛型的Task<T>。
TPL讓我們可以用線性的方式去編寫異步程序,不在需要像EAP中那樣搞一堆回調(diào)、邏輯跳來跳去了。await現(xiàn)在已經(jīng)被JavaScript借鑒走了!
我們用await實現(xiàn)下面的一個功能:先下載A,如果下載的內(nèi)容長度大于100則下載B,否則下載C。代碼實現(xiàn)如下:
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風(fēng)格的API。這里建議盡可能的使用TPL風(fēng)格的。
五、如何編寫異步方法
在上面的示例中,我們都是使用的.NET框架給我們提供好的API,如果我們想自己編寫異步方法該怎么辦?因為目前Task使用最廣泛,所以我們這里以TPL為例講解如何編寫自己的異步方法。
首先異步方法的返回值要是Task<T>類型,方法里面返回一個Task類型,潛規(guī)則(不要求)是方法名字以Async結(jié)尾,這樣就會知道這是一個異步方法。看下面的例子:
/// <summary>
/// 自定義異步方法,返回類型是string
/// </summary>
/// <returns></returns>
private Task<string> TestTask()
{
return Task.Run<string>(() =>
{
// 讓線程休眠3秒,模擬一個耗時的操作
Thread.Sleep(3000);
return "這是一個異步方法";
});
}這樣就編寫好了一個異步方法。那么怎么使用這個異步方法呢?使用方法跟我們使用.NET框架提供的異步方法一樣,看下面調(diào)用異步方法的代碼:
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>
/// 調(diào)用自定義的異步方法
/// </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;
}
}
}程序輸出結(jié)果:

這樣就可以調(diào)用自己寫的異步方法了。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
.NET的動態(tài)編譯與WS服務(wù)調(diào)用詳解
這篇文章介紹了.NET的動態(tài)編譯與WS服務(wù)調(diào)用詳解,有需要的朋友可以參考一下,希望對你有所幫助2013-07-07
WPF使用代碼創(chuàng)建數(shù)據(jù)模板DataTemplate
本文詳細講解了WPF使用代碼創(chuàng)建數(shù)據(jù)模板DataTemplate的方法,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-02-02
詳解.NET中的加密算法總結(jié)(自定義加密Helper類續(xù))
這篇文章主要介紹了詳解.NET中的加密算法總結(jié)(自定義加密Helper類續(xù)) ,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2016-12-12
使用.NET升級助手將.NET?Framework項目升級為.NET?6
這篇文章介紹了使用.NET升級助手將.NET?Framework項目升級為.NET?6的方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
.NET的基元類型包括什么及Unmanaged和Blittable類型詳解
這篇文章主要介紹了.NET的基元類型包括什么及Unmanaged和Blittable類型詳解,Unmanaged類型可以理解不涉及托管對象引用的值類型,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2023-06-06
asp.net下 jquery jason 高效傳輸數(shù)據(jù)
jquery jason 高效傳輸數(shù)據(jù)轉(zhuǎn)自網(wǎng)上稍有修改2009-03-03

