網站首頁 編程語言 正文
一、引言
.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開始讀取 Tasktask = 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開始讀取 // Tasktask = 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開始讀取 // Tasktask = 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; } } }
程序輸出結果:
這樣就可以調用自己寫的異步方法了。
原文鏈接:https://www.cnblogs.com/dotnet261010/p/12339966.html
相關推薦
- 2023-12-10 Invalid bound statement (not found): 各種原因
- 2022-11-19 C語言結構體成員賦值的深拷貝與淺拷貝詳解_C 語言
- 2023-06-18 C#零基礎開發中最重要的概念總結_C#教程
- 2022-08-11 boost.asio框架系列之socket編程_C 語言
- 2022-02-07 解決 laravels 無法接收微信回調的參數問題
- 2023-01-02 Kotlin中空判斷與問號和感嘆號標識符使用方法_Android
- 2023-07-25 使用Http請求調用第三方API
- 2023-02-27 一文搞懂Golang?值傳遞還是引用傳遞_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支