C#异步编程-------异步编程模型(APM)
術語解釋:
APM???????????????異步編程模型,?Asynchronous Programming Model
EAP????????????????基于事件的異步編程模式,?Event-based Asynchronous Pattern
TAP????????????????基于任務的異步編程模式,?Task-based Asynchronous Pattern
?
一、異步編程
APM即異步編程模型(Asynchronous Programming Model),大家在寫代碼的時候,或者查看.NET 的類庫的時候,肯定會經??吹胶褪褂靡訠eginXXX和EndXXX類似的方法,其實你在使用這些方法的時候,你就在使用異步編程模型來編寫程序。異步編寫模型是一種模式,該模式允許用更少的線程去做更多的操作,.NET Framework很多類也實現了該模式,同時我們也可以自定義類來實現該模式,(也就是在自定義的類中實現返回類型為IAsyncResult接口的BeginXXX方法和EndXXX方法),另外委托類型也定義了BeginInvoke和EndInvoke方法,并且我們使用WSDL.exe和SvcUtil.exe工具來生成Web服務的代理類型時,也會生成使用了APM的BeginXxx和EndXxx方法。下面就具體就拿FileStream類的BeginRead和EndRead方法來介紹下下異步編程模型的實現。
讀取的同步方法:
public?override?int?Read(byte[] array,?int?offset,?int?count );
讀取過程中,執行此方法會阻塞主線程,最明顯的就是造成界面卡死。
讀取的異步方法:
// 開始異步讀操作
// 前面的3個參數和同步方法代表的意思一樣,這里就不說了,可以看到這里多出了2個參數
// userCallback代表當異步IO操作完成時,你希望由一個線程池線程執行的方法,該方法必須匹配AsyncCallback委托
// stateObject代表你希望轉發給回調方法的一個對象的引用,在回調方法中,可以查詢IAsyncResult接口的AsyncState屬性來訪問該對象
public?override?IAsyncResult BeginRead(byte[] array,?int?offset,?int?numBytes, AsyncCallback userCallback, Object stateObject)
?
讀取完成后如果不需要進行其他操作,可以設置為null
?
EndXxx方法——結束異步操作介紹
前面介紹完了BeginXxx方法,我們看到所有BeginXxx方法返回的都是實現了IAsyncResult接口的一個對象,改返回值并不是對應的同步方法所期望的返回值。為獲得期望的返回值,我們需要調用對應的EndXxx方法來結束異步操作,并向該方法傳遞IAsyncResult對象,EndXxx方法的返回類型就是和同步方法一樣的。例如,FileStream的EndRead方法返回一個Int32來代表從文件流中實際讀取的字節數。
異步調用的返回值:對于訪問異步操作的返回值,APM提供了四種方式供開發人員選擇:
在調用BeginXxx方法的線程上調用EndXxx方法來得到異步操作的返回值,但是這種方式會阻塞調用線程,只到操作完成之后調用線程才繼續運行。
查詢IAsyncResult的AsyncWaitHandle屬性,從而得到WaitHandle,然后再調用它的WaitOne方法來使一個線程阻塞并等待被調用線程操作完成,再調用EndXxx方法來獲得操作的返回值。
循環查詢IAsyncResult的IsComplete屬性,操作完成后再調用EndXxx方法來獲得操作返回的結果。
使用 AsyncCallback委托來指定操作完成時要調用的方法,在操作完成后調用的方法中調用EndXxx操作來獲得異步操作的結果。(最為常用)
異步編程模型的本質:
異步編程模型這個模式,是微軟利用委托和線程池幫助我們實現的一個模式(該模式利用一個線程池線程去執行一個操作,在FileStream類BeginRead方法中就是執行一個讀取文件操作,該線程池線程會立即將控制權返回給調用線程,此時線程池線程在后臺進行這個異步操作;異步操作完成之后,通過回調函數來獲取異步操作返回的結果。此時就是利用委托的機制。所以說異步編程模式是利用委托和線程池線程搞出來的模式,包括后面的基于事件的異步編程和基于任務的異步編程,還有C# 5中的async和await關鍵字,都是利用這委托和線程池搞出來的。他們的本質都一樣,后面方法使異步編程更加簡單。)
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Runtime.Remoting.Messaging; using System.Text; using System.Windows.Forms;//https://msdn.microsoft.com/zh-cn/library/system.net.httpwebrequest.begingetresponse.aspxnamespace WindowsFormsApplication1 {public class RequestState{// This class stores the State of the request.const int BUFFER_SIZE = 1024;public StringBuilder requestData;public byte[] BufferRead;public HttpWebRequest request;public HttpWebResponse response;public Stream streamResponse;public Stream filestream;public string savepath;public RequestState(){BufferRead = new byte[BUFFER_SIZE];requestData = new StringBuilder("");request = null;streamResponse = null;}}public partial class Form1 : Form{// 定義用來實現異步編程的委托private delegate string AsyncMethodCaller(string fileurl);public Form1(){InitializeComponent();tbUrl.Text = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe";// 允許跨線程調用// 實際開發中不建議這樣做的,違背了.NET 安全規范CheckForIllegalCrossThreadCalls = false;}private void btnDownLoad_Click(object sender, EventArgs e){rtbContent.Text = "Download............";if (tbUrl.Text == string.Empty){MessageBox.Show("Please input valid download file url");return;}AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync);methodCaller.BeginInvoke(tbUrl.Text.Trim(), GetResult, null);}// 同步下載文件的方法// 該方法會阻塞主線程,使用戶無法對界面進行操作// 在文件下載完成之前,用戶甚至都不能關閉運行的程序。private string DownLoadFileSync(string url){// Create an instance of the RequestStateRequestState requestState = new RequestState();try{// Initialize an HttpWebRequest objectHttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);// assign HttpWebRequest instance to its request field.requestState.request = myHttpWebRequest;requestState.response = (HttpWebResponse)myHttpWebRequest.GetResponse();requestState.streamResponse = requestState.response.GetResponseStream();int readSize = requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);while (readSize > 0){requestState.filestream.Write(requestState.BufferRead, 0, readSize);readSize = requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);}// 執行該方法的線程是線程池線程,該線程不是與創建richTextBox控件的線程不是一個線程// 如果不把 CheckForIllegalCrossThreadCalls 設置為false,該程序會出現“不能跨線程訪問控件”的異常return string.Format("The Length of the File is: {0}", requestState.filestream.Length) + string.Format("\nDownLoad Completely, Download path is: {0}", requestState.savepath);}catch (Exception e){return string.Format("Exception occurs in DownLoadFileSync method, Error Message is:{0}", e.Message);}finally{//requestState.response.Close();//requestState.filestream.Close();}}// 異步操作完成時執行的方法private void GetResult(IAsyncResult result){AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;// 調用EndInvoke去等待異步調用完成并且獲得返回值// 如果異步調用尚未完成,則 EndInvoke 會一直阻止調用線程,直到異步調用完成string returnstring = caller.EndInvoke(result);//sc.Post(ShowState,resultvalue);rtbContent.Text = returnstring;}} } using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Runtime.Remoting.Messaging; using System.Text; using System.Windows.Forms;//https://msdn.microsoft.com/zh-cn/library/system.net.httpwebrequest.begingetresponse.aspxnamespace WindowsFormsApplication1 {public class RequestState{// This class stores the State of the request.const int BUFFER_SIZE = 1024;public StringBuilder requestData;public byte[] BufferRead;public HttpWebRequest request;public HttpWebResponse response;public Stream streamResponse;public Stream filestream;public string savepath;public RequestState(){BufferRead = new byte[BUFFER_SIZE];requestData = new StringBuilder("");request = null;streamResponse = null;}}public partial class Form1 : Form{// 定義用來實現異步編程的委托private delegate string AsyncMethodCaller(string fileurl);public Form1(){InitializeComponent();tbUrl.Text = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe";// 允許跨線程調用// 實際開發中不建議這樣做的,違背了.NET 安全規范CheckForIllegalCrossThreadCalls = false;}private void btnDownLoad_Click(object sender, EventArgs e){rtbContent.Text = "Download............";if (tbUrl.Text == string.Empty){MessageBox.Show("Please input valid download file url");return;}AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync);methodCaller.BeginInvoke(tbUrl.Text.Trim(), GetResult, null);}// 同步下載文件的方法// 該方法會阻塞主線程,使用戶無法對界面進行操作// 在文件下載完成之前,用戶甚至都不能關閉運行的程序。private string DownLoadFileSync(string url){// Create an instance of the RequestStateRequestState requestState = new RequestState();try{// Initialize an HttpWebRequest objectHttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);// assign HttpWebRequest instance to its request field.requestState.request = myHttpWebRequest;requestState.response = (HttpWebResponse)myHttpWebRequest.GetResponse();requestState.streamResponse = requestState.response.GetResponseStream();int readSize = requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);while (readSize > 0){requestState.filestream.Write(requestState.BufferRead, 0, readSize);readSize = requestState.streamResponse.Read(requestState.BufferRead, 0, requestState.BufferRead.Length);}// 執行該方法的線程是線程池線程,該線程不是與創建richTextBox控件的線程不是一個線程// 如果不把 CheckForIllegalCrossThreadCalls 設置為false,該程序會出現“不能跨線程訪問控件”的異常return string.Format("The Length of the File is: {0}", requestState.filestream.Length) + string.Format("\nDownLoad Completely, Download path is: {0}", requestState.savepath);}catch (Exception e){return string.Format("Exception occurs in DownLoadFileSync method, Error Message is:{0}", e.Message);}finally{//requestState.response.Close();//requestState.filestream.Close();}}// 異步操作完成時執行的方法private void GetResult(IAsyncResult result){AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;// 調用EndInvoke去等待異步調用完成并且獲得返回值// 如果異步調用尚未完成,則 EndInvoke 會一直阻止調用線程,直到異步調用完成string returnstring = caller.EndInvoke(result);//sc.Post(ShowState,resultvalue);rtbContent.Text = returnstring;}} }當我們調用實現基于事件的異步模式的類的 XxxAsync方法時,即代表開始了一個異步操作,該方法調用完之后會使一個線程池線程去執行耗時的操作,所以當UI線程調用該方法時,當然也就不會堵塞UI線程了。并且基于事件的異步模式是建立了APM的基礎之上的(這也是我在上一專題中詳解介紹APM的原因),而APM又是建立了在委托之上的
二、基于事件的異步編程
基于事件的C#異步編程模式是比IAsyncResult模式更高級的一種異步編程模式,也被用在更多的場合。該異步模式具有以下優點:
·????????????????? “在后臺”執行耗時任務(例如下載和數據庫操作),但不會中斷您的應用程序。
·????????????????? 同時執行多個操作,每個操作完成時都會接到通知(在通知中可以區分是完成了哪個操作)。
·????????????????? 等待資源變得可用,但不會停止(“掛起”)您的應用程序。
·????????????????? 使用熟悉的事件和委托模型與掛起的異步操作通信。
對于相對簡單的應用程序可以直接用 .Net 2.0 新增的 BackgroundWorker 組件來很方便的實現,對于更復雜的異步應用程序則需要自己實現一個符合基于事件的C#異步編程模式的類。在實現基于事件的異步模式的設計前,需要了解基于事件的異步模式的實現原理是什么?;谑录漠惒侥J叫枰韵氯齻€類型的幫助。
AsyncOperation:提供了對異步操作的生存期進行跟蹤的功能,包括操作進度通知和操作完成通知,并確保在正確的線程或上下文中調用客戶端的事件處理程序。
public void Post(SendOrPostCallback d,Object arg);
public void PostOperationCompleted(SendOrPostCallback d,Object arg);
通過在異步輔助代碼中調用Post方法把進度和中間結果報告給用戶,如果是取消異步任務或提示異步任務已完成,則通過調用PostOperationCompleted方法結束異步操作的跟蹤生命期。在PostOperationCompleted方法調用后,AsyncOperation對象變得不再可用,再次訪問將引發異常。在此有個問題:在該異步模式中,通過AsyncOperation的Post函數來通知進度的時候,是如何使SendOrPostCallback委托在UI線程上執行的?針對該問題下文有具體分析。
AsyncOperationManager:為AsyncOperation對象的創建提供了便捷方式,通過CreateOperation方法可以創建多個AsyncOperation實例,實現對多個異步操作進行跟蹤。?
WindowsFormsSynchronizationContext:該類繼承自SynchronizationContext類型,提供 Windows 窗體應用程序模型的同步上下文。該類型是基于事件異步模式通信的核心。之所以說該類型是基于事件異步模式的通信核心,是因為該類型解決了“保證SendOrPostCallback委托在UI線程上執行”的問題。它是如何解決的?請看AsyncOperation類型的Post方法的實現:
?
/// <summary> /// AsyncOperation類型的Post方法的實現 /// </summary> public void Post(SendOrPostCallback d, object arg) { this.VerifyNotCompleted(); this.VerifyDelegateNotNull(d); this.syncContext.Post(d, arg); }?
在AsyncOperation類型的Post方法中,直接調用了SynchronizationContext類型的Post方法,再看該Post方法的實現:
?
/// <summary> /// WindowsFormsSynchronizationContext類型的Post方法的實現 /// </summary> public override void Post(SendOrPostCallback d, object state) { if (this.controlToSendTo != null) { this.controlToSendTo.BeginInvoke(d, new object[] { state }); //此處保證了SendOrPostCallBack委托在UI線程上執行 } }?
例子:
?
using System; using System.Collections.Generic; using System.Text; using System.ComponentModel; using System.Collections.Specialized; using System.Threading; namespace test { /// <summary> /// 任務1的進度通知代理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public delegate void Work1ProgressChangedEventHandler(object sender, Work1ProgressChangedEventArgs e); /// <summary> /// 任務1的進度通知參數 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public delegate void Work1CompletedEventHandler(object sender, Work1CompletedEventArgs e); public class BasedEventAsyncWorker { private delegate void WorkerEventHandler(int maxNumber, AsyncOperation asyncOp); private HybridDictionary userStateToLifetime = new HybridDictionary(); public BasedEventAsyncWorker() { } #region DoWork1的基于事件的異步調用 public void DoWork1Async(object userState, int maxNumber) { AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); //userStateToLifetime有可能會同時被多線程訪問,在此需要lock進行同步處理 lock (userStateToLifetime.SyncRoot) { if (userStateToLifetime.Contains(userState)) { throw new ArgumentException( "userState parameter must be unique", "userState"); } userStateToLifetime[userState] = asyncOp; } //異步開始任務1 WorkerEventHandler workerDelegate = new WorkerEventHandler(DoWork1); workerDelegate.BeginInvoke(maxNumber, asyncOp, null, null); } private void DoWork1(int maxNumber, AsyncOperation asyncOp) { Exception e = null; //判斷該userState的任務仍在處理中 if (!TaskCanceled(asyncOp.UserSuppliedState)) { try { int n = 0; int percentage = 0; while (n < maxNumber && !TaskCanceled(asyncOp.UserSuppliedState)) { Thread.Sleep(100); //模擬耗時操作 percentage = (int)((float)n / (float)maxNumber * 100); Work1ProgressChangedEventArgs progressChanageArgs = new Work1ProgressChangedEventArgs(maxNumber, percentage, asyncOp.UserSuppliedState); //任務1的進度通知 asyncOp.Post(new SendOrPostCallback(Work1ReportProgressCB), progressChanageArgs); n++; } } catch (Exception ex) { e = ex; } } this.Work1Complete(e, TaskCanceled(asyncOp.UserSuppliedState), asyncOp); } private void Work1Complete(Exception exception, bool canceled, AsyncOperation asyncOp) { if (!canceled) { lock (userStateToLifetime.SyncRoot) { userStateToLifetime.Remove(asyncOp.UserSuppliedState); } } Work1CompletedEventArgs e = new Work1CompletedEventArgs(exception, canceled, asyncOp.UserSuppliedState); //通知指定的任務已經完成 asyncOp.PostOperationCompleted(new SendOrPostCallback(Work1CompleteCB), e); //調用 PostOperationCompleted 方法來結束異步操作的生存期。 //為某個特定任務調用此方法后,再調用其相應的 AsyncOperation 對象會引發異常。 } private void Work1ReportProgressCB(object state) { Work1ProgressChangedEventArgs e = state as Work1ProgressChangedEventArgs; OnWork1ProgressChanged(e); } private void Work1CompleteCB(object state) { Work1CompletedEventArgs e = state as Work1CompletedEventArgs; OnWork1Completed(e); } #region Work1的進度通知和任務完成的事件 public event Work1ProgressChangedEventHandler Work1ProgressChanged; protected virtual void OnWork1ProgressChanged(Work1ProgressChangedEventArgs e) { Work1ProgressChangedEventHandler temp = this.Work1ProgressChanged; if (temp != null) { temp(this, e); } } public event Work1CompletedEventHandler Work1Completed; protected virtual void OnWork1Completed(Work1CompletedEventArgs e) { Work1CompletedEventHandler temp = this.Work1Completed; if (temp != null) { temp(this, e); } } #endregion #endregion /// <summary> /// 取消指定userState的任務執行 /// </summary> /// <param name="userState"></param> public void CancelAsync(object userState) { AsyncOperation asyncOp = userStateToLifetime[userState] as AsyncOperation; if (asyncOp != null) { lock (userStateToLifetime.SyncRoot) { userStateToLifetime.Remove(userState); } } } /// <summary> /// 判斷指定userState的任務是否已經被結束。返回值:true 已經結束; false 還沒有結束 /// </summary> /// <param name="userState"></param> /// <returns></returns> private bool TaskCanceled(object userState) { return (userStateToLifetime[userState] == null); } } public class Work1ProgressChangedEventArgs :ProgressChangedEventArgs { private int totalWork = 1; public Work1ProgressChangedEventArgs(int totalWork, int progressPercentage, object userState) : base(progressPercentage, userState) { this.totalWork = totalWork; } /// <summary> /// Work1的總工作量 /// </summary> public int TotalWork { get { return totalWork; } } } public class Work1CompletedEventArgs : AsyncCompletedEventArgs { public Work1CompletedEventArgs(Exception e, bool canceled, object state) : base(e, canceled, state) { } } }?
三、基于任務的異步編程
基于任務的異步模式(Task-based Asynchronous Pattern,TAP)之所以被微軟所推薦,主要就它使用簡單,基于任務的異步模式使用單個方法來表示異步操作的開始和完成,然而異步編程模型(APM)卻要求BeginXxx和EndXxx兩個方法來分別表示異步操作的開始和完成(這樣使用起來就復雜了),然而,基于事件的異步模式(EAP)要求具有Async后綴的方法和一個或多個事件、事件處理程序和事件參數??吹竭@里,是不是大家都有這樣一個疑問的——我們怎樣區分.NET類庫中的類實現了基于任務的異步模式呢? 這個識別方法很簡單,當看到類中存在TaskAsync為后綴的方法時就代表該類實現了TAP,?并且基于任務的異步模式同樣也支持異步操作的取消和進度的報告的功能,但是這兩個實現都不像EAP中實現的那么復雜,因為如果我們要自己實現EAP的類,我們需要定義多個事件和事件處理程序的委托類型和事件的參數(具體可以查看上一專題中的BackgroundWorker剖析部分),但是在TAP實現中,我們只需要通過向異步方法傳入CancellationToken參數,因為在異步方法內部會對這個參數的IsCancellationRequested屬性進行監控,當異步方法收到一個取消請求時,異步方法將會退出執行(具體這點可以使用反射工具查看WebClient的DownloadDataTaskAsync方法,同時也可以參考我后面部分自己實現基于任務的異步模式的異步方法。),在TAP中,我們可以通過IProgress<T>接口來實現進度報告的功能,具體實現可以參考我后面的程序部分。
目前我還沒有找到在.NET 類庫中實現了基于任務的異步模式的哪個類提供進度報告的功能,下面的將為大家演示這個實現,并且也是這個程序的亮點,同時通過自己實現TAP的異步方法來進一步理解基于任務的異步模式。
如何基于任務異步編程
// Download File// CancellationToken 參數賦值獲得一個取消請求// progress參數負責進度報告private void DownLoadFile(string url, CancellationToken ct, IProgress<int> progress){HttpWebRequest request = null;HttpWebResponse response = null;Stream responseStream = null;int bufferSize = 2048;byte[] bufferBytes = new byte[bufferSize];try{request = (HttpWebRequest)WebRequest.Create(url);if (DownloadSize != 0){request.AddRange(DownloadSize);}response = (HttpWebResponse)request.GetResponse();responseStream = response.GetResponseStream();int readSize = 0;while (true){// 收到取消請求則退出異步操作if (ct.IsCancellationRequested == true){MessageBox.Show(String.Format("下載暫停,下載的文件地址為:{0}\n 已經下載的字節數為: {1}字節", downloadPath, DownloadSize));response.Close();filestream.Close();sc.Post((state) =>{this.btnStart.Enabled = true;this.btnPause.Enabled = false;}, null);// 退出異步操作break;}readSize = responseStream.Read(bufferBytes, 0, bufferBytes.Length);if (readSize > 0){DownloadSize += readSize;int percentComplete = (int)((float)DownloadSize / (float)totalSize * 100);filestream.Write(bufferBytes, 0, readSize);// 報告進度progress.Report(percentComplete);}else{MessageBox.Show(String.Format("下載已完成,下載的文件地址為:{0},文件的總字節數為: {1}字節", downloadPath, totalSize));sc.Post((state) =>{this.btnStart.Enabled = false;this.btnPause.Enabled = false;}, null);response.Close();filestream.Close();break;}} }catch (AggregateException ex){// 因為調用Cancel方法會拋出OperationCanceledException異常// 將任何OperationCanceledException對象都視為以處理ex.Handle(e => e is OperationCanceledException);}}
四、Async和await關鍵字(C#5.0,.net4.5以后)
”async”和”await”:你用我用,才有用;你用我不用,等于沒用。
五、異步編程幾種方式的比較,引同行文章http://www.cnblogs.com/durow/p/4826653.html
0x00 引言
之前寫程序的時候在遇到一些比較花時間的操作例如HTTP請求時,總是會new一個Thread處理。對XxxxxAsync()之類的方法也沒去了解過,倒也沒遇到什么大問題。最近因為需求要求用DevExpress寫界面,跑起來后發現比Native控件效率差好多。這才想到之前看到的“金科玉律”:不要在UI線程上執行界面無關的操作,因此集中看了下C#的異步操作,分享一下自己的比較和總結。
0x01 測試方法
IDE:VS2015 Community
.NET版本:4.5
使用函數隨機休眠100到500毫秒來模擬耗時任務,并返回任務花費的時間,在UI線程上調用這個方法會造成阻塞,導致UI假死,因此需要通過異步方式執行這個任務,并在信息輸出區域顯示花費的時間。
?
主界面中通過各種不同按鈕測試不同類型的異步操作
?
0x02 使用Thread進行異步操作
使用ThreadPool進行異步操作的方法如下所示,需要注意的就是IsBackground默認為false,也就是該線程對調用它的線程不產生依賴,當調用線程退出時該線程也不會結束。因此需要將IsBackground設置為true以指明該線程是后臺線程,這樣當主線程退出時該線程也會結束。另外跨線程操作UI還是要借助Dispatcher.BeginInvoke(),如果需要阻塞UI線程可以使用Dispatcher.Invoke()。
?
0x03 使用ThreadPool進行異步操作
ThreadPool(線程池)的出現主要就是為了提高線程的復用(類似的還有訪問數據庫的連接池)。線程的創建是開銷比較大的行為,為了達到較好的交互體驗,開發中可能會大量使用異步操作,特別是需要頻繁進行大量的短時間的異步操作時,頻繁創建和銷毀線程會在造成很多資源的浪費。而通過在線程池中存放一些線程,當需要新建線程執行操作時就從線程池中取出一個已經存在的空閑線程使用,如果此時沒有空閑線程,且線程池中的線程數未達到線程池上限,則新建一個線程,使用完成后再放回到線程池中。這樣可以極大程度上省去線程創建的開銷。線程池中線程的最小和最大數都可以指定,不過多數情況下無需指定,CLR有一套管理線程池的策略。ThreadPool的使用非常簡單,代碼如下所示??缇€程操作UI仍需借助Dispatcher。
?
0x04 使用Task進行異步操作
Task進行異步操作時也是從線程池中獲取線程進行操作,不過支持的操作更加豐富一些。而且Task<T>可以支持返回值,通過Task的ContinueWith()可以在Task執行結束后將返回值傳入以進行操作,但在ContinueWith中跨線程操作UI仍需借助Dispatcher。另外Task也可以直接使用靜態方法Task.Run<T>()執行異步操作。
?
0x05 使用async/await進行異步操作
這個是C#5中的新特性,當遇到await時,會從線程池中取出一個線程異步執行await等待的操作,然后方法立即返回。等異步操作結束后回到await所在的地方接著往后執行。await需要等待async Task<T>類型的函數。詳細的使用方法可參考相關資料,測試代碼如下所示。異步結束后的會返回到調用線程,所以修改UI不需要Dispatcher。
?
也可以把TestTask包裝成async方法,這樣就可以使用上圖中注釋掉的兩行代碼進行處理。包裝后的異步方法如下所示:
?
async/await也是從線程池中取線程,可實現線程復用,而且代碼簡潔容易閱讀,異步操作返回后會自動返回調用線程,是執行異步操作的首選方式。而且雖然是C#5的新特性,但C#4可以通過下載升級包來支持async/await。
0x06 關于效率
以上嘗試的方法除了直接使用Thread之外,其他幾種都是直接或間接使用線程池來獲取線程的。從理論上來分析,創建線程時要給線程分配??臻g,線程銷毀時需要回收內存,創建線程也會增加CPU的工作。因此可以連續創建線程并記錄消耗的時間來測試性能。測試代碼如下所示:
?
當測試Thread時每次測試在連續創建線程時內存和CPU都會有個小突起,不過在線程結束后很快就會降下去,在我的電腦上連續創建100個線程大概花費120-130毫秒。如圖所示:
?
測試結果:
?
使用基于線程池的方法創建線程時,有時第一次會稍慢一些,應該是線程池內線程不足,時間開銷在0-15毫秒,第一次創建內存也會上升。后面再測試時時間開銷為0毫秒,內存表現也很平穩,CPU開銷分布比較平均。測試結果如圖所示:
?
0x07 結論
在執行異步操作時應使用基于線程池的操作,從代碼的簡潔程度和可讀性上優先使用async/await方式。對于較老的.NET版本可以使用Task或ThreadPool。符合以下情況的可以使用Thread:
1、線程創建后需要持續工作到主線程退出的。這種情況下就算使用線程池線程也不會歸還,實現不了復用,可以使用Thread。
2、線程在主線程退出后仍需要執行的,這種情況使用線程池線程無法滿足需求,需要使用Thread并制定IsBackground為false(默認)。
0x08 相關下載
測試程序代碼在:https://github.com/durow/TestArea/tree/master/AsyncTest/AsyncTest
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的C#异步编程-------异步编程模型(APM)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 想把房贷利率转成LPR的要抓紧了,截止期
- 下一篇: 信用卡逾期多少天上征信