1、異步編程模型(APM) html
2、基於事件的異步編程模式(EAP)
spring
APM即異步編程模式的簡寫(Asynchronous Programming Model)。你們在寫代碼的時候或者查看.NET 的類庫的時候確定會常常看到和使用以BeginXXX和EndXXX相似的方法,其實你在使用這些方法的時候,你就再使用異步編程模型來編寫程序。NET Framework不少類也實現了該模式,同時咱們也能夠自定義類來實現該模式,(也就是在自定義的類中實現返回類型爲IAsyncResult接口的BeginXXX方法和EndXXX方法),另外委託類型也定義了BeginInvoke和EndInvoke方法。緩存
下面就具體就拿FileStream類的BeginRead和EndRead方法來介紹下下異步編程模型的實現。服務器
當須要讀取文件中的內容時,咱們一般會採用FileStream的同步方法Read來讀取,該同步方法的定義爲:異步
// 從文件流中讀取字節塊並將該數據寫入給定的字節數組中 // array表明把讀取的字節塊寫入的緩存區 // offset表明array的字節偏量,將在此處讀取字節 // count 表明最多讀取的字節數 public override int Read(byte[] array, int offset, int count )
該同步方法會堵塞執行的線程。能夠經過BeginRead方法來實現異步編程,使讀取操做再也不堵塞UI線程。BeginRead方法表明異步執行Read操做,並返回實現IAsyncResult接口的對象,該對象存儲着異步操做的信息,下面就看下BeginRead方法的定義,看看與同步Read的方法區別在哪裏的.async
// 開始異步讀操做 // 前面的3個參數和同步方法表明的意思同樣,這裏就不說了,能夠看到這裏多出了2個參數 // userCallback表明當異步IO操做完成時,你但願由一個線程池線程執行的方法,該方法必須匹配AsyncCallback委託 // stateObject表明你但願轉發給回調方法的一個對象的引用,在回調方法中,能夠查詢IAsyncResult接口的AsyncState屬性來訪問該對象 public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject )
從上面的代碼中能夠看出異步方法和同步方法的區別,若是你在使用該異步方法時,不但願異步操做完成後調用任何代碼,你能夠把userCallback參數設置爲null。該異步方法子因此不會堵塞UI線程是由於調用該方法後,該方法會當即把控制權返回給調用線程(若是是UI線程來調用該方法時,即返回給UI線程),然而同步卻不是這樣,同步方法是等該操做完成以後返回讀取的內容以後才返回給調用線程,從而致使在操做完成以前調用線程就一直等待狀態。ide
前面介紹完了BeginXxx方法,咱們看到全部BeginXxx方法返回的都是實現了IAsyncResult接口的一個對象,並非對應的同步方法那樣直接獲得結果。此時咱們須要調用對應的EndXxx方法來結束異步操做,並向該方法傳遞IAsyncResult對象,EndXxx方法的返回類型就是和同步方法同樣的。例如,FileStream的EndRead方法返回一個Int32來表明從文件流中實際讀取的字節數。異步編程
// 摘要: 等待掛起的異步讀取完成。 // asyncResult:對要完成的掛起異步請求的引用。 // 返回結果: 從流中讀取的字節數. public virtual int EndRead(IAsyncResult asyncResult);
對於訪問異步操做的結果,APM的首選方式是:
使用 AsyncCallback委託來指定操做完成時要調用的方法,在操做完成後調用的方法中調用EndXxx操做來得到異步操做的結果。
其實異步編程模型這個模式,就是微軟利用委託和線程池幫助咱們實現的一個模式(該模式利用一個線程池線程去執行一個操做,在FileStream類BeginRead方法中就是執行一個讀取文件操做,該線程池線程會當即將控制權返回給調用線程,此時線程池線程在後臺進行這個異步操做;異步操做完成以後,經過回調函數來獲取異步操做返回的結果。此時就是利用委託的機制。因此說異步編程模式時利用委託和線程池線程搞出來的模式,包括後面的基於事件的異步編程和基於任務的異步編程,還有C# 5中的async和await關鍵字,都是利用這委託和線程池搞出來的。他們的本質其實都是同樣的,只是後面提出來的使異步編程更加簡單罷了。)
存儲請求的狀態類:
// 這個類存儲請求的狀態 public class RequestState { public int BufferSize = 1024; public string savepath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\TAP.docx"; public byte[] BufferRead; public HttpWebRequest request; public HttpWebResponse response; public Stream streamResponse; public FileStream filestream; public RequestState() { BufferRead = new byte[BufferSize]; request = null; streamResponse = null; if (File.Exists(savepath)) { File.Delete(savepath); } filestream = new FileStream(savepath, FileMode.OpenOrCreate); } }
主程序:
private static void Main(string[] args) { string downUrl = "http://download.microsoft.com/download/5/B/9/5B924336-AA5D-4903-95A0-56C6336E32C9/TAP.docx"; // 同步下載文件 //在下載操做完成以後咱們才能夠看到"Start DownLoad File......." 消息顯示 DownLoadFileSync(downUrl); //異步下載文件 //在下載操做完成以前咱們就能夠看到"Start DownLoad File......." 消息顯示 // DownloadFileAsync(downUrl); Console.WriteLine("Start DownLoad File........."); Console.ReadLine(); }
調用的是同步方法時,此時會堵塞主線程,直到文件的下載操做被完成以後主線程才繼續執行後面的代碼,下面是下載文件的同步方法:
private static void DownLoadFileSync(string url) { // 建立一個 RequestState 實例 RequestState req = new RequestState(); try { // 初始化一個 HttpWebRequest 對象 HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); // 指派 HttpWebRequest實例到requestState的request字段. req.request = myHttpWebRequest; req.response = (HttpWebResponse)myHttpWebRequest.GetResponse(); req.streamResponse = req.response.GetResponseStream(); int readSize = req.streamResponse.Read
(req.BufferRead, 0, req.BufferRead.Length); while (readSize > 0) { req.filestream.Write(req.BufferRead, 0, readSize); readSize = req.streamResponse.Read
(req.BufferRead, 0, req.BufferRead.Length); } Console.WriteLine("\nThe Length of the File is: {0}", req.filestream.Length); Console.WriteLine("DownLoad Completely, Download path is: {0}", req.savepath); } catch (Exception e) { Console.WriteLine("Error Message is:{0}", e.Message); } finally { req.response.Close(); req.filestream.Close(); } }
使用同步方法下載文件的運行結果爲:
控制檯程序演示如何使用APM來現異步編程:
private static void DownloadFileAsync(string url) { try { HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); RequestState requestState = new RequestState(); requestState.request = myHttpWebRequest; myHttpWebRequest.BeginGetResponse(new AsyncCallback(ResponseCallback), requestState); } catch (Exception e) { Console.WriteLine("Error Message is:{0}", e.Message); } } //每一個異步操做完成時,將調用下面的方法 private static void ResponseCallback(IAsyncResult callbackresult) { // 獲取 RequestState 對象 RequestState req = (RequestState)callbackresult.AsyncState; HttpWebRequest myHttpRequest = req.request; // 結束一個對英特網資源的的異步請求 req.response = (HttpWebResponse)myHttpRequest.EndGetResponse(callbackresult); //從服務器獲取響應流 Stream responseStream = req.response.GetResponseStream(); req.streamResponse = responseStream; //異步讀取流到字節數組 IAsyncResult asynchronousRead = responseStream.BeginRead
(req.BufferRead, 0, req.BufferRead.Length, ReadCallBack, req); } // 寫字節數組到 FileStream private static void ReadCallBack(IAsyncResult asyncResult) { try { // 獲取 RequestState 對象 RequestState req = (RequestState)asyncResult.AsyncState; //從RequestState對象中獲取 Response Stream Stream responserStream = req.streamResponse; // int readSize = responserStream.EndRead(asyncResult); if (readSize > 0) { req.filestream.Write(req.BufferRead, 0, readSize); responserStream.BeginRead(req.BufferRead, 0, req.BufferRead.Length, ReadCallBack, req);//循環調用ReadCallBack方法。 } else { Console.WriteLine("\nThe Length of the File is: {0}", req.filestream.Length); Console.WriteLine("DownLoad Completely, Download path is: {0}", req.savepath); req.response.Close(); req.filestream.Close(); } } catch (Exception e) { Console.WriteLine("Error Message is:{0}", e.Message); } }
運行結果爲(從運行結果也能夠看出,在主線程中調用 DownloadFileAsync(downUrl)方法時,DownloadFileAsync(downUrl)方法中的req.BeginGetResponse調用被沒有阻塞調用線程(即主線程),而是當即返回到主線程,是主線程後面的代碼能夠當即執行)
在前面的介紹中已經提到委託類型也會定義了BeginInvoke方法和EndInvoke方法,因此委託類型也實現了異步編程模型,因此可使用委託的BeginInvoke和EndInvoke方法來回調同步方法從而實現異步編程。由於調用委託的BeginInvoke方法來執行一個同步方法時,此時會使用線程池線程回調這個同步方法並當即返回到調用線程中,因爲耗時操做在另一個線程上運行,因此執行BeginInvoke方法的主線程就不會被堵塞。下面實如今線程池線程中如何更新GUI線程中窗體。
// 定義用來實現異步編程的委託 private delegate string AsyncMethodCaller(string fileurl);
private void btnDownLoad_Click(object sender, EventArgs e) { AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync);//DownLoadFileSync爲同步下載文件的方法 methodCaller.BeginInvoke
(txbUrl.Text.Trim(),GetResult
, null); } // 異步操做完成時執行的方法 private void GetResult(IAsyncResult result) { AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;// 或者(AsyncMethodCaller)result.AsyncState; // 調用EndInvoke去等待異步調用完成而且得到返回值 // 若是異步調用還沒有完成,則 EndInvoke 會一直阻止調用線程,直到異步調用完成
try{
string returnstring = caller.EndInvoke(result);
}
catch (Exception ex){}
// 而後Invoke方法來使更新GUI操做方法由GUI 線程去執行 Invoke(new MethodInvoker(delegate() { rtbState.Text = returnstring; btnDownLoad.Enabled = true; })); }
運行的結果爲:
上例子中使用了控件的Invoke方式進行異步回調訪問控件的方法,其背後是經過得到GUI線程的同步上文對象,而後同步調用同步上下文對象的post方法把要調用的方法交給GUI線程去處理。
// 定義用來實現異步編程的委託 private delegate string AsyncMethodCaller(string fileurl); // 定義顯示狀態的委託 private delegate void ShowStateDelegate(string value); private ShowStateDelegate showStateCallback; SynchronizationContext sc; public MainForm() { InitializeComponent(); txbUrl.Text = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe"; showStateCallback = new ShowStateDelegate(ShowState); } private void btnDownLoad_Click(object sender, EventArgs e) { AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync); methodCaller.BeginInvoke(txbUrl.Text.Trim(), GetResult, null); // 捕捉調用線程的同步上下文派生對象sc =
SynchronizationContext.Current; } // 異步操做完成時執行的方法 private void GetResult(IAsyncResult result) { AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate; string returnstring = caller.EndInvoke(result); // 經過得到GUI線程的同步上下文的派生對象,而後調用Post方法來使更新GUI操做方法由GUI 線程去執行 sc.Post(ShowState, returnstring); } // 顯示結果到richTextBox,由於該方法是由GUI線程執行的,因此固然就能夠訪問窗體控件了 private void ShowState(object result) { rtbState.Text = result.ToString(); btnDownLoad.Enabled = true; }
假如如今有這樣的一個需求,咱們須要從3個txt文件中讀取字符,而後進行倒序,前提是不能阻塞主線程。若是不用task的話我可能會用工做線程去監視一個bool變量來判斷文件是否所有讀取完畢,而後再進行倒序,我也說了,相對task來講仍是比較麻煩的,這裏我就用task來實現。
static byte[] b; static void Main() { string[] array = { "C://1.txt", "C://2.txt", "C://3.txt" }; List<Task<string>> taskList = new List<Task<string>>(3); foreach (var item in array) { taskList.Add(ReadAsyc(item)); } Task.Factory.ContinueWhenAll(taskList.ToArray(), i => { string result = string.Empty; //獲取各個task返回的結果 foreach (var item in i) { result += item.Result; } //倒序 String content = new String(result.OrderByDescending(j => j).ToArray()); Console.WriteLine("倒序結果:"+content); }); Console.WriteLine("我是主線程,我不會被阻塞"); Console.ReadKey(); } //異步讀取 static Task<string> ReadAsyc(string path) { FileInfo info = new FileInfo(path); byte[] b = new byte[info.Length]; FileStream fs = new FileStream(path, FileMode.Open); Task<int> task = Task<int>.Factory.FromAsync(fs.BeginRead, fs.EndRead, b, 0, b.Length, null, TaskCreationOptions.None); //返回當前task的執行結果 return task.ContinueWith(i => { return i.Result > 0 ? Encoding.Default.GetString(b) : string.Empty; }, TaskContinuationOptions.ExecuteSynchronously); }
到這裏本專題關於異步編程模型的介紹就結束了,異步編程模型(APM)雖然是.NET 1.0中提出來的一個模式,相對於如今來講是舊了點,而且微軟如今官方也代表在最新的代碼中不推薦使用該模型來實現異步的應用程序,而是推薦使用基於任務的異步編程模型來實現異步的應用程序,可是我我的認爲,正是由於它是.NET 1.0中提出的來,而且如今來看確實有些舊了, 因此咱們才更應該好好研究下它,由於後面提出的EAP和TAP微軟作了更多的封裝,是咱們對異步編程的本質都不清楚的(其實它們的本質都是使用線程池和委託機制的,具體能夠查看前面的相關部分),而且系統學習下異步編程,也可讓咱們對新的異步編程模型的所帶來的好處有更可直觀的認識。在後面的一專題我將帶你們全面認識下基於事件的異步編程模型(EAP)。