.NET1.0版本中的異步編程模型(APM)
一、概念
.NET 1.0提出了APM(Asynchronous Programming Model)即異步編程模式。
.NET的類庫有以BeginXXX和EndXXX類似的方法,就是使用異步編程模型。
NET Framework很多類也實(shí)現(xiàn)了該模式,同時(shí)我們也可以自定義類來實(shí)現(xiàn)該模式,即在自定義的類中實(shí)現(xiàn)返回類型為IAsyncResult接口的BeginXXX方法和EndXXX方法,另外委托類型也定義了BeginInvoke和EndInvoke方法。
異步編程模型的本質(zhì)
利用委托和線程池幫助我們實(shí)現(xiàn)異步編程模型模式。
該模式利用一個線程池線程去執(zhí)行一個操作,在FileStream類BeginRead方法中就是執(zhí)行一個讀取文件操作,該線程池線程會立即將控制權(quán)返回給調(diào)用線程,此時(shí)線程池線程在后臺進(jìn)行這個異步操作;
異步操作完成之后,通過回調(diào)函數(shù)來獲取異步操作返回的結(jié)果,此時(shí)就是利用委托的機(jī)制。
1、BeginXxx方法——開始執(zhí)行異步操作介紹
當(dāng)需要讀取文件中的內(nèi)容時(shí),我們通常會采用FileStream的同步方法Read來讀取,該同步方法的定義為:
// 從文件流中讀取字節(jié)塊并將該數(shù)據(jù)寫入給定的字節(jié)數(shù)組中 public override int Read(byte[] array, int offset, int count )
該同步方法會堵塞執(zhí)行的線程。
可以通過BeginRead方法來實(shí)現(xiàn)異步編程,使讀取操作不再堵塞UI線程。BeginRead方法代表異步執(zhí)行Read操作,并返回實(shí)現(xiàn)IAsyncResult接口的對象,該對象存儲著異步操作的信息。
// 開始異步讀操作 // 前面的3個參數(shù)和同步方法代表的意思一樣,這里就不說了,可以看到這里多出了2個參數(shù) // userCallback代表當(dāng)異步IO操作完成時(shí),你希望由一個線程池線程執(zhí)行的方法,該方法必須匹配AsyncCallback委托 // stateObject代表你希望轉(zhuǎn)發(fā)給回調(diào)方法的一個對象的引用,在回調(diào)方法中,可以查詢IAsyncResult接口的AsyncState屬性來訪問該對象 public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject)
從上面的代碼中可以看出異步方法和同步方法的區(qū)別,如果你在使用該異步方法時(shí),不希望異步操作完成后調(diào)用任何代碼,你可以把userCallback參數(shù)設(shè)置為null。
2、EndXxx方法——結(jié)束異步操作介紹
前面介紹完了BeginXxx方法,我們看到所有BeginXxx方法返回的都是實(shí)現(xiàn)了IAsyncResult接口的一個對象,并不是對應(yīng)的同步方法那樣直接得到結(jié)果。
此時(shí)我們需要調(diào)用對應(yīng)的EndXxx方法來結(jié)束異步操作,并向該方法傳遞IAsyncResult對象,EndXxx方法的返回類型就是和同步方法一樣的。例如,FileStream的EndRead方法返回一個Int32來代表從文件流中實(shí)際讀取的字節(jié)數(shù)。
// 摘要: 等待掛起的異步讀取完成。 // asyncResult:對要完成的掛起異步請求的引用。 // 返回結(jié)果: 從流中讀取的字節(jié)數(shù). public virtual int EndRead(IAsyncResult asyncResult);
對于訪問異步操作的結(jié)果,APM的首選方式是:
使用 AsyncCallback委托來指定操作完成時(shí)要調(diào)用的方法,在操作完成后調(diào)用的方法中調(diào)用EndXxx操作來獲得異步操作的結(jié)果。
二、APM示例:
代碼:
private static void Main(string[] args) { string downUrl = "http://download.microsoft.com/download/5/B/9/5B924336-AA5D-4903-95A0-56C6336E32C9/TAP.docx"; DownLoadFileSync(downUrl); //同步下載文件,在下載操作完成之后我們才可以看到"Start DownLoad File......." 消息顯示 DownloadFileAsync(downUrl); //異步下載文件,在下載操作完成之前我們就可以看到"Start DownLoad File......." 消息顯示 Console.WriteLine("開始下載文件........."); Console.ReadLine(); } //同步下載文件 private static void DownLoadFileSync(string url) { RequestState req = new RequestState(); // 創(chuàng)建一個 RequestState 實(shí)例 try { HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); // 初始化一個 HttpWebRequest 對象 req.request = myHttpWebRequest; // 指派 HttpWebRequest實(shí)例到requestState的request字段. 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("\n此文件的長度是: {0}", req.filestream.Length); Console.WriteLine("下載完成,下載路徑是: {0}", req.savepath); } catch (Exception e) { Console.WriteLine("錯誤信息:{0}", e.Message); } finally { req.response.Close(); req.filestream.Close(); } } //異步下載文件 private static void DownloadFileAsync(string url) { try { HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); RequestState req = new RequestState(); req.request = myHttpWebRequest; myHttpWebRequest.BeginGetResponse(new AsyncCallback(ResponseCallback), req); } catch (Exception e) { Console.WriteLine("錯誤信息:{0}", e.Message); } } //每個異步操作完成時(shí),將調(diào)用下面的方法 private static void ResponseCallback(IAsyncResult callbackresult) { RequestState req = (RequestState)callbackresult.AsyncState; // 獲取 RequestState 對象 HttpWebRequest myHttpRequest = req.request; req.response = (HttpWebResponse)myHttpRequest.EndGetResponse(callbackresult); // 結(jié)束一個對英特網(wǎng)資源的的異步請求 Stream responseStream = req.response.GetResponseStream(); //從服務(wù)器獲取響應(yīng)流 req.streamResponse = responseStream; IAsyncResult asynchronousRead = responseStream.BeginRead(req.BufferRead, 0, req.BufferRead.Length, ReadCallBack, req);//異步讀取流到字節(jié)數(shù)組 } // 寫字節(jié)數(shù)組到 FileStream private static void ReadCallBack(IAsyncResult asyncResult) { try { RequestState req = (RequestState)asyncResult.AsyncState; // 獲取 RequestState 對象 Stream responserStream = req.streamResponse; //從RequestState對象中獲取 Response Stream 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);//循環(huán)調(diào)用ReadCallBack方法。 } else { Console.WriteLine("\n此文件的長度是: {0}", req.filestream.Length); Console.WriteLine("下載完成,下載路徑是: {0}", req.savepath); req.response.Close(); req.filestream.Close(); } } catch (Exception e) { Console.WriteLine("錯誤信息:{0}", e.Message); } } //存儲請求的狀態(tài)類 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); } }
運(yùn)行結(jié)果:
使用同步方法下載文件的運(yùn)行結(jié)果為:(從運(yùn)行結(jié)果也可以看出,調(diào)用的是同步方法時(shí),此時(shí)會堵塞主線程,直到文件的下載操作被完成之后主線程才繼續(xù)執(zhí)行后面的代碼)
使用APM異步編程:運(yùn)行結(jié)果為(從運(yùn)行結(jié)果也可以看出,在主線程中調(diào)用 DownloadFileAsync(downUrl)方法時(shí),DownloadFileAsync(downUrl)方法中的req.BeginGetResponse調(diào)用被沒有阻塞調(diào)用線程(即主線程),而是立即返回到主線程,是主線程后面的代碼可以立即執(zhí)行)
三、委托實(shí)例的異步調(diào)用(BeginInvoke、EndInvoke方法)
在前面的介紹中已經(jīng)提到委托類型也會定義了BeginInvoke方法和EndInvoke方法,所以委托類型也實(shí)現(xiàn)了異步編程模型,所以可以使用委托的BeginInvoke和EndInvoke方法來回調(diào)同步方法從而實(shí)現(xiàn)異步編程。
因?yàn)檎{(diào)用委托的BeginInvoke方法來執(zhí)行一個同步方法時(shí),此時(shí)會使用線程池線程回調(diào)這個同步方法并立即返回到調(diào)用線程中,由于耗時(shí)操作在另外一個線程上運(yùn)行,所以執(zhí)行BeginInvoke方法的主線程就不會被堵塞。
下面實(shí)現(xiàn)在線程池線程中如何更新GUI線程中窗體。
// 定義用來實(shí)現(xiàn)異步編程的委托 private delegate string AsyncMethodCaller(string fileurl); private void btnDownLoad_Click(object sender, EventArgs e) { AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync);//DownLoadFileSync為同步下載文件的方法 methodCaller.BeginInvoke(this.txbUrl.Text.Trim(), GetResult, null); } // 異步操作完成時(shí)執(zhí)行的方法 private void GetResult(IAsyncResult result) { AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;// 或者(AsyncMethodCaller)result.AsyncState; string returnstring = caller.EndInvoke(result); // 調(diào)用EndInvoke去等待異步調(diào)用完成并且獲得返回值,如果異步調(diào)用尚未完成,則 EndInvoke 會一直阻止調(diào)用線程,直到異步調(diào)用完成 // 然后Invoke方法來使更新GUI操作方法由GUI 線程去執(zhí)行 this.Invoke(new MethodInvoker(() => { rtbState.Text = returnstring; btnDownLoad.Enabled = true; })); }
運(yùn)行的結(jié)果為:
上例子中使用了控件的Invoke方式進(jìn)行異步回調(diào)訪問控件的方法,其背后是通過獲得GUI線程的同步上文對象SynchronizationContext,然后同步調(diào)用同步上下文對象的post方法把要調(diào)用的方法交給GUI線程去處理。
四、task實(shí)例
假如現(xiàn)在有這樣的一個需求,我們需要從3個txt文件中讀取字符,然后進(jìn)行倒序,前提是不能阻塞主線程。
如果不用task的話我可能會用工作線程去監(jiān)視一個bool變量來判斷文件是否全部讀取完畢,然后再進(jìn)行倒序,我也說了,相對task來說還是比較麻煩的,這里我就用task來實(shí)現(xiàn)。
private 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返回的結(jié)果 foreach (var item in i) { result += item.Result; } //倒序 String content = new String(result.OrderByDescending(j => j).ToArray()); Console.WriteLine("倒序結(jié)果:" + content); }); Console.WriteLine("我是主線程,我不會被阻塞"); Console.ReadKey(); } //異步讀取 private 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); //返回當(dāng)前task的執(zhí)行結(jié)果 return task.ContinueWith(i => { return i.Result > 0 ? Encoding.Default.GetString(b) : string.Empty; }, TaskContinuationOptions.ExecuteSynchronously); }
到此這篇關(guān)于.NET1.0異步編程模型(APM)的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
.Net結(jié)構(gòu)型設(shè)計(jì)模式之適配器模式(Adapter)
這篇文章介紹了.Net結(jié)構(gòu)型設(shè)計(jì)模式之適配器模式(Adapter),文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05那些年,我還在學(xué)asp.net(一) 學(xué)習(xí)筆記
那些年到此,基本學(xué)習(xí)了前端的基本知識,那些年的第四課就是asp.net,當(dāng)然那時(shí)看了很多教程,比如說:天轟穿,當(dāng)然天轟穿說得比較多,如面向?qū)ο螅珻#知識,由于當(dāng)時(shí)上過C++,所以就沒有看這些,直接從asp.net開始,主要是學(xué)習(xí)一下asp.net用到的一些基本控件2012-03-03IIS7 應(yīng)用程序池的 托管管道模式與集成模式小結(jié)
而 IIS 7 完全整合 .NET 之后,架構(gòu)的處理順序有了很大的不同(如下圖),最主要的原因就是 ASP.NET 從 IIS 插件(ISAPI extension)的角色,進(jìn)入了 IIS 核心,而且也能以 ASP.NET 模塊負(fù)責(zé)處理 IIS 7 的諸多類型要求。2011-02-02WPF框架Prism中導(dǎo)航Navigation用法介紹
這篇文章介紹了WPF框架Prism中導(dǎo)航Navigation的用法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02一步步打造漂亮的新聞列表(無刷新分頁、內(nèi)容預(yù)覽)第二步
由于我們僅僅是項(xiàng)目中的一個小部分,但也差不多按照以上的順序進(jìn)行開發(fā),這是一個良好的習(xí)慣。我們將概要設(shè)計(jì)和詳細(xì)設(shè)計(jì)放在一起。2010-07-07