本專題概要:
我們提供的服務(wù)有:網(wǎng)站制作、成都網(wǎng)站制作、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、尚志ssl等。為1000多家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的尚志網(wǎng)站制作公司引言
你知道APM嗎?
你想知道如何使用異步編程模型編寫代碼嗎?
使用委托也可以實(shí)現(xiàn)異步編程,你知道否?
小結(jié)
一、引言
在前面的C#基礎(chǔ)知識系列中介紹了從C#1.0——C#4.0中一些主要特性,然而.NET 4.5更新,除了提供了一些新的類和一些新的模板外,對于C#語言也做了一定的更新,最重要的就是.NET 4.5(對應(yīng)于C#5.0)中提供了async和await兩個(gè)關(guān)鍵字,這兩個(gè)關(guān)鍵字是我們實(shí)現(xiàn)異步編程更加容易了,其實(shí)早在.NET 1.0開始微軟就對異步編程做了相應(yīng)的支持——即異步編程模型(APM), 之后在.NET 2.0中又提出了基于事件的異步編程模型(EAP),.NET 4.0中又提出了基于任務(wù)的異步編程模型(TAP)。所以為了幫助大家全面理解.NET類庫對異步編程的支持,這里我把我學(xué)習(xí)異步編程的一些體會和理解分享出來,希望對大家在學(xué)習(xí)的過程中有所幫助。
在開始講解APM之前,我想先分享一下Visual Studio 版本、C# 版本和.NET 版本的一個(gè)對應(yīng)關(guān)系。之所以在這里分享這個(gè)對應(yīng)關(guān)系,是因?yàn)樵贑#基礎(chǔ)知識系列的文章發(fā)布之后,有些初學(xué)者對.NET版本和C#語言特性之間的對應(yīng)關(guān)系有點(diǎn)不清楚,有時(shí)候會弄混淆了。并且通過這個(gè)對應(yīng)關(guān)系,也可以幫助大家對C#和.NET 類庫有個(gè)全面的把控,可以幫助大家理清楚C#和.NET 類庫中各個(gè)知識點(diǎn),使他們可以對號入坐。具體他們的之間對應(yīng)關(guān)系見下表:
C# 版本 | .NET Framework版本 | Visual Studio版本 | 發(fā)布日期 | 特性 |
C# 1.0 | .NET Framework 1.0 | Visual Studio .NET 2002 | 2002.1 | 委托 事件 APM |
C# 1.1 | .NET Framework 1.1 | Visual Studio .NET 2003 | 2003.4 | |
C# 2.0 | .NET Framework 2.0 | Visual Studio 2005(開始命名為Visual Studio) | 2005.11 | 泛型 匿名方法 迭代器 可空類型 |
C# 3.0 | .NET Framework 3.0 .NET Framework 3.5 | Visual Studio 2008 | 2007.11 | 隱式類型的部變量 對象集合初始化 自動實(shí)現(xiàn)屬性 匿名類型 擴(kuò)展方法 查詢表達(dá)式 Lambda表達(dá)式 表達(dá)式樹 分部類和方法 Linq |
C# 4.0 | .NET Framework 4.0 | Visual Studio 2010 | 2010.4 | 動態(tài)綁定 命名和可選參數(shù) 泛型的協(xié)變和逆變 互操作性 |
C# 5.0 | .NET Framework 4.5 | Visual Studio 2012 | 2012.8 | 異步和等待(async和await) 調(diào)用方信息(Caller Information) |
二、你知道APM嗎?
APM即異步編程模型的簡寫(Asynchronous Programming Model),大家在寫代碼的時(shí)候或者查看.NET 的類庫的時(shí)候肯定會經(jīng)常看到和使用以BeginXXX和EndXXX類似的方法,其實(shí)你在使用這些方法的時(shí)候,你就再使用異步編程模型來編寫程序。異步編寫模型是一種模式,該模式允許用更少的線程去做更多的操作,.NET Framework很多類也實(shí)現(xiàn)了該模式,同時(shí)我們也可以自定義類來實(shí)現(xiàn)該模式,(也就是在自定義的類中實(shí)現(xiàn)返回類型為IAsyncResult接口的BeginXXX方法和EndXXX方法),另外委托類型也定義了BeginInvoke和EndInvoke方法,并且我們使用WSDL.exe和SvcUtil.exe工具來生成Web服務(wù)的代理類型時(shí),也會生成使用了APM的BeginXxx和EndXxx方法。下面就具體就拿FileStream類的BeginRead和EndRead方法來介紹下下異步編程模型的實(shí)現(xiàn)。
BeginXxx方法——開始執(zhí)行異步操作介紹
當(dāng)需要讀取文件中的內(nèi)容時(shí),我們通常會采用FileStream的同步方法Read來讀取,該同步方法的定義為:
// 從文件流中讀取字節(jié)塊并將該數(shù)據(jù)寫入給定的字節(jié)數(shù)組中 // array代表把讀取的字節(jié)塊寫入的緩存區(qū) // offset代表array的字節(jié)偏量,將在此處讀取字節(jié) // count 代表最多讀取的字節(jié)數(shù) public override int Read(byte[] array, int offset, int count )
該同步方法會堵塞執(zhí)行的線程,當(dāng)一個(gè)WinForm程序需要實(shí)現(xiàn)讀取一個(gè)大文件的內(nèi)容然后把內(nèi)容顯示在界面時(shí),如果我們調(diào)用該方法去讀取文件的內(nèi)容時(shí),此時(shí)Read方法會堵塞UI線程,在讀取文件內(nèi)容沒有完成之前,用戶不能對窗體進(jìn)行任何的操作,包括關(guān)閉應(yīng)用程序,此時(shí)用戶看到的該窗體會出現(xiàn)無法響應(yīng),這樣就給用戶帶來不好一個(gè)用戶體驗(yàn),從用戶角度來看是用戶體驗(yàn)不好,此時(shí)我們自己解決問題的思路肯定是——能不能讓讀取文件操作在另外一個(gè)線程中執(zhí)行,這樣就不會堵塞UI線程,這時(shí)候UI線程繼續(xù)做屬于自己的事情,即響應(yīng)用戶的操作。不錯(cuò),微軟也肯定也想到了這個(gè)解決方案的,并且在實(shí)際操作中也是這么做的,即通過BeginRead方法來實(shí)現(xiàn)異步編程,使讀取操作不再堵塞UI線程。BeginRead方法代表異步執(zhí)行Read操作,并返回實(shí)現(xiàn)IAsyncResult接口的對象,該對象存儲著異步操作的信息,下面就看下BeginRead方法的定義,看看與同步Read的方法區(qū)別在哪里的.
// 開始異步讀操作 // 前面的3個(gè)參數(shù)和同步方法代表的意思一樣,這里就不說了,可以看到這里多出了2個(gè)參數(shù) // userCallback代表當(dāng)異步IO操作完成時(shí),你希望由一個(gè)線程池線程執(zhí)行的方法,該方法必須匹配AsyncCallback委托 // stateObject代表你希望轉(zhuǎn)發(fā)給回調(diào)方法的一個(gè)對象的引用,在回調(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。該異步方法子所以不會堵塞UI線程是因?yàn)檎{(diào)用該方法后,該方法會立即把控制權(quán)返回給調(diào)用線程(如果是UI線程來調(diào)用該方法時(shí),即返回給UI線程),然而同步卻不是這樣,同步方法是等該操作完成之后返回讀取的內(nèi)容之后才返回給調(diào)用線程,從而導(dǎo)致在操作完成之前調(diào)用線程就一直等待狀態(tài)。
EndXxx方法——結(jié)束異步操作介紹
前面介紹完了BeginXxx方法,我們看到所有BeginXxx方法返回的都是實(shí)現(xiàn)了IAsyncResult接口的一個(gè)對象,并不是對應(yīng)的同步方法所要得到的結(jié)果的。此時(shí)我們需要調(diào)用對應(yīng)的EndXxx方法來結(jié)束異步操作,并向該方法傳遞IAsyncResult對象,EndXxx方法的返回類型就是和同步方法一樣的。例如,FileStream的EndRead方法返回一個(gè)Int32來代表從文件流中實(shí)際讀取的字節(jié)數(shù)。
對于訪問異步操作的結(jié)果,APM提供了四種方式供開發(fā)人員選擇:
在調(diào)用BeginXxx方法的線程上調(diào)用EndXxx方法來得到異步操作的結(jié)果,但是這種方式會阻塞調(diào)用線程,知道操作完成之后調(diào)用線程才繼續(xù)運(yùn)行
查詢IAsyncResult的AsyncWaitHandle屬性,從而得到WaitHandle,然后再調(diào)用它的WaitOne方法來使一個(gè)線程阻塞并等待操作完成再調(diào)用EndXxx方法來獲得操作的結(jié)果。
循環(huán)查詢IAsyncResult的IsComplete屬性,操作完成后再調(diào)用EndXxx方法來獲得操作返回的結(jié)果。
使用 AsyncCallback委托來指定操作完成時(shí)要調(diào)用的方法,在操作完成后調(diào)用的方法中調(diào)用EndXxx操作來獲得異步操作的結(jié)果。
在上面的4種方式中,第4種方式是APM的選方式,因?yàn)榇藭r(shí)不會阻塞執(zhí)行BeginXxx方法的線程,然而其他三種都會阻塞調(diào)用線程,相當(dāng)于效果和使用同步方法是一樣,個(gè)人感覺根本失去了異步編程的特點(diǎn),所以其他三種方式可以簡單了解下,在實(shí)際異步編程中都是使用委托的方式。
通過上面的介紹,大家應(yīng)該對異步編程模型有了進(jìn)一步的了解了吧,要識別某個(gè)類是否實(shí)現(xiàn)了異步編程模型,只需要看是不是有BeginXxx方法(當(dāng)然返回類型需要是IAsyncResult)和EndXxx方法。其實(shí)異步編程模型這個(gè)模式,就是微軟利用委托和線程池幫助我們實(shí)現(xiàn)的一個(gè)模式(該模式利用一個(gè)線程池線程去執(zhí)行一個(gè)操作,在FileStream類BeginRead方法中就是執(zhí)行一個(gè)讀取文件操作,該線程池線程會立即將控制權(quán)返回給調(diào)用線程,此時(shí)線程池線程在后臺進(jìn)行這個(gè)異步操作;異步操作完成之后,通過回調(diào)函數(shù)來獲取異步操作返回的結(jié)果。此時(shí)就是利用委托的機(jī)制。所以說異步編程模式時(shí)利用委托和線程池線程搞出來的模式,包括后面的基于事件的異步編程和基于任務(wù)的異步編程,還有C# 5中的async和await關(guān)鍵字,都是利用這委托和線程池搞出來的。他們的本質(zhì)其實(shí)都是一樣的,只是后面提出來的使異步編程更加簡單罷了。)
既然這里講到了FileStream對象,這里就提出一個(gè)關(guān)于該類值得注意的地方的:
FileStream對象默認(rèn)情況下是同步打開操作系統(tǒng)句柄,當(dāng)我們創(chuàng)建一個(gè)FileStream對象沒有為其指定FileOptions.Asynchronous參數(shù)或者沒有顯示指定useAsync為true時(shí),Windows 操作系統(tǒng)會以同步的方法執(zhí)行所有的文件操作,即使此時(shí)你還是可以調(diào)用BeginRead方法。但是這樣對于你的應(yīng)用程序,操作只是表面上是異步執(zhí)行的,但FileStream類在內(nèi)部會用另一個(gè)線程模擬異步行為。
同樣道理,當(dāng)創(chuàng)建的FileStream對象指定了FileOptions.Asynchronous參數(shù)時(shí),然后我們?nèi)匀豢梢哉{(diào)用Read同步方法,此時(shí)在內(nèi)部,F(xiàn)ileStream類會開始一個(gè)異步操作,并立即使調(diào)用線程進(jìn)入睡眠狀態(tài),知道操作完成才會喚醒,通過這樣來模擬同步行為。因此在使用FileStream對象時(shí),需要先決定是同步執(zhí)行還是異步執(zhí)行。并顯示地指定FileOptions.Asynchronous參數(shù)或useAsync參數(shù)。
三、你想知道如何使用異步編程模型編寫代碼嗎?
介紹了這么久的異步編程模型,大家肯定很迫不及待地想使用異步編程模型來改寫自己的同步應(yīng)用程序或者實(shí)現(xiàn)一個(gè)異步的應(yīng)用程序。下面就通過一個(gè)例子來演示如何使用APM來現(xiàn)異步編程(該程序也實(shí)現(xiàn)了一個(gè)同步方法,為了讓大家更好地體會同步線程和異步線程的區(qū)別,本程序的實(shí)現(xiàn)是一個(gè)控制臺程序,大家也可以很好地一直與WinForm應(yīng)用程序和WPF程序):
#region use APM to download file asynchronously private static void DownloadFileAsync(string url) { try { // Initialize an HttpWebRequest object HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url); // Create an instance of the RequestState and assign HttpWebRequest instance to its request field. RequestState requestState = new RequestState(); requestState.request = myHttpWebRequest; myHttpWebRequest.BeginGetResponse(new AsyncCallback(ResponseCallback), requestState); } catch (Exception e) { Console.WriteLine("Error Message is:{0}",e.Message); } } // The following method is called when each asynchronous operation completes. private static void ResponseCallback(IAsyncResult callbackresult) { // Get RequestState object RequestState myRequestState = (RequestState)callbackresult.AsyncState; HttpWebRequest myHttpRequest = myRequestState.request; // End an Asynchronous request to the Internet resource myRequestState.response = (HttpWebResponse)myHttpRequest.EndGetResponse(callbackresult); // Get Response Stream from Server Stream responseStream = myRequestState.response.GetResponseStream(); myRequestState.streamResponse = responseStream; IAsyncResult asynchronousRead = responseStream.BeginRead(myRequestState.BufferRead, 0, myRequestState.BufferRead.Length, ReadCallBack, myRequestState); } // Write bytes to FileStream private static void ReadCallBack(IAsyncResult asyncResult) { try { // Get RequestState object RequestState myRequestState = (RequestState)asyncResult.AsyncState; // Get Response Stream from Server Stream responserStream = myRequestState.streamResponse; // int readSize = responserStream.EndRead(asyncResult); if (readSize > 0) { myRequestState.filestream.Write(myRequestState.BufferRead, 0, readSize); responserStream.BeginRead(myRequestState.BufferRead, 0, myRequestState.BufferRead.Length, ReadCallBack, myRequestState); } else { Console.WriteLine("\nThe Length of the File is: {0}", myRequestState.filestream.Length); Console.WriteLine("DownLoad Completely, Download path is: {0}", myRequestState.savepath); myRequestState.response.Close(); myRequestState.filestream.Close(); } } catch (Exception e) { Console.WriteLine("Error Message is:{0}", e.Message); } } #endregion
運(yùn)行結(jié)果為(從運(yùn)行結(jié)果也可以看出,在主線程中調(diào)用 DownloadFileAsync(downUrl)方法時(shí),DownloadFileAsync(downUrl)方法中的myHttpWebRequest.BeginGetResponse調(diào)用被沒有阻塞調(diào)用線程(即主線程),而是立即返回到主線程,是主線程后面的代碼可以立即執(zhí)行)
如果我們調(diào)用的是同步方法時(shí),此時(shí)會堵塞主線程,直到文件的下載操作被完成之后主線程才繼續(xù)執(zhí)行后面的代碼,下面是下載文件的同步方法:
#region Download File Synchrously private static void DownLoadFileSync(string url) { // Create an instance of the RequestState RequestState requestState=new RequestState(); try { // Initialize an HttpWebRequest object HttpWebRequest 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); } Console.WriteLine("\nThe Length of the File is: {0}", requestState.filestream.Length); Console.WriteLine("DownLoad Completely, Download path is: {0}", requestState.savepath); } catch (Exception e) { Console.WriteLine("Error Message is:{0}", e.Message); } finally { requestState.response.Close(); requestState.filestream.Close(); } } #endregion
使用同步方法下載文件的運(yùn)行結(jié)果為(大家可以對照兩個(gè)方式的結(jié)果就可以明顯看出他們的區(qū)別了。):
四、使用委托也可以實(shí)現(xiàn)異步編程,你知道否?
在前面的介紹中已經(jīng)提到委托類型也會定義了BeginInvoke方法和EndInvoke方法,所以委托類型也實(shí)現(xiàn)了異步編程模型,所以可以使用委托的BeginInvoke和EndInvoke方法來回調(diào)同步方法從而實(shí)現(xiàn)異步編程。因?yàn)檎{(diào)用委托的BeginInvoke方法來執(zhí)行一個(gè)同步方法時(shí),此時(shí)會使用線程池線程回調(diào)這個(gè)同步方法并立即返回到調(diào)用線程中,由于耗時(shí)操作在另外一個(gè)線程上運(yùn)行,所以執(zhí)行BeginInvoke方法的主線程就不會被堵塞。但是這里存在的一個(gè)問題時(shí),因?yàn)橥椒椒ㄔ诹硗庖粋€(gè)線程中執(zhí)行的,然而我們怎么把同步方法執(zhí)行的狀態(tài)反應(yīng)到UI界面上來呢?因?yàn)樵贕UI應(yīng)用程序(包括Windows窗體,WPF和Silverlight)中,創(chuàng)建窗口的線程是唯一能夠?qū)δ莻€(gè)窗口進(jìn)行更新的線程,所以在執(zhí)行同步方法的線程就不能對窗口中的控件進(jìn)行操作,也就不能把方法允許的結(jié)果反應(yīng)到窗體上了。這里有兩種解決方案,一種是設(shè)置控件的CheckForIllegalCrossThreadCalls 屬性為false,設(shè)置為false的意思代表允許跨線程調(diào)用,(這種方式雖然可以解決該問題,但是不推薦,因?yàn)樗`背了.NET安全規(guī)范);第二種就是使用SynchronizationContext基類,該類記錄著線程的同步上下文對象,我們可以通過在GUI線程中調(diào)用SynchronizationContext.Current屬性來獲得GUI線程的同步上下文,然后當(dāng)線程池線程需要更新窗體時(shí),可以調(diào)用保存的SynchronizationContext派生對象的Post方法(Post方法會將回調(diào)函數(shù)送到GUI線程的隊(duì)列中,每個(gè)線程都有各自的操作隊(duì)列的,線程的執(zhí)行都是從這個(gè)隊(duì)列中拿方法去執(zhí)行),向Post方法傳遞要由GUI線程調(diào)用的方法(該方法的定義要匹配SendOrPostCallback委托的簽名),還需要想Post方法傳遞一個(gè)要傳給回調(diào)方法的參數(shù)。
4.1 使用委托實(shí)現(xiàn)更好的用戶體驗(yàn)——不堵塞UI線程
雖然第一種方案是一種不推薦的方案,但是我覺得有些朋友還是不知道怎么實(shí)現(xiàn)的,所以在這部分就用具體的代碼來實(shí)現(xiàn)下,并且該實(shí)現(xiàn)也可以與使用同步上下文對象的方式進(jìn)行對比,這樣大家就可以更加了解如何使用委托來進(jìn)行異步編程了。下面就具體看實(shí)現(xiàn)代碼吧:
View Code // 定義用來實(shí)現(xiàn)異步編程的委托 private delegate string AsyncMethodCaller(string fileurl); public Mainform() { InitializeComponent(); txbUrl.Text = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe"; // 允許跨線程調(diào)用 // 實(shí)際開發(fā)中不建議這樣做的,違背了.NET 安全規(guī)范 CheckForIllegalCrossThreadCalls = false; } private void btnDownLoad_Click(object sender, EventArgs e) { rtbState.Text = "Download............"; if (txbUrl.Text == string.Empty) { MessageBox.Show("Please input valid download file url"); return; } AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync); methodCaller.BeginInvoke(txbUrl.Text.Trim(), GetResult, null); } // 同步下載文件的方法 // 該方法會阻塞主線程,使用戶無法對界面進(jìn)行操作 // 在文件下載完成之前,用戶甚至都不能關(guān)閉運(yùn)行的程序。 private string DownLoadFileSync(string url) { // Create an instance of the RequestState RequestState requestState = new RequestState(); try { // Initialize an HttpWebRequest object HttpWebRequest 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); } // 執(zhí)行該方法的線程是線程池線程,該線程不是與創(chuàng)建richTextBox控件的線程不是一個(gè)線程 // 如果不把 CheckForIllegalCrossThreadCalls 設(shè)置為false,該程序會出現(xiàn)“不能跨線程訪問控件”的異常 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(); } } // 異步操作完成時(shí)執(zhí)行的方法 private void GetResult(IAsyncResult result) { AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate; // 調(diào)用EndInvoke去等待異步調(diào)用完成并且獲得返回值 // 如果異步調(diào)用尚未完成,則 EndInvoke 會一直阻止調(diào)用線程,直到異步調(diào)用完成 string returnstring= caller.EndInvoke(result); //sc.Post(ShowState,resultvalue); rtbState.Text = returnstring; }
運(yùn)行的結(jié)果為:
4.2 在線程中訪問另一個(gè)線程創(chuàng)建的控件
這部分將使用同步上下文的方式來實(shí)現(xiàn)在線程池線程中如何更新GUI線程中窗體,因?yàn)樵诔绦虼a部分都有詳細(xì)的解釋,這里就直接貼代碼了
public partial class MainForm : Form { // 定義用來實(shí)現(xiàn)異步編程的委托 private delegate string AsyncMethodCaller(string fileurl); // 定義顯示狀態(tài)的委托 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) { rtbState.Text = "Download............"; btnDownLoad.Enabled = false; if (txbUrl.Text == string.Empty) { MessageBox.Show("Please input valid download file url"); return; } AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileSync); methodCaller.BeginInvoke(txbUrl.Text.Trim(), GetResult, null); // 捕捉調(diào)用線程的同步上下文派生對象 sc = SynchronizationContext.Current; } // 同步下載文件的方法 // 該方法會阻塞主線程,使用戶無法對界面進(jìn)行操作 // 在文件下載完成之前,用戶甚至都不能關(guān)閉運(yùn)行的程序。 private string DownLoadFileSync(string url) { // Create an instance of the RequestState RequestState requestState = new RequestState(); try { // Initialize an HttpWebRequest object HttpWebRequest 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); } // 執(zhí)行該方法的線程是線程池線程,該線程不是與創(chuàng)建richTextBox控件的線程不是一個(gè)線程 // 如果不把 CheckForIllegalCrossThreadCalls 設(shè)置為false,該程序會出現(xiàn)“不能跨線程訪問控件”的異常 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(); } } // 異步操作完成時(shí)執(zhí)行的方法 private void GetResult(IAsyncResult result) { AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate; // 調(diào)用EndInvoke去等待異步調(diào)用完成并且獲得返回值 // 如果異步調(diào)用尚未完成,則 EndInvoke 會一直阻止調(diào)用線程,直到異步調(diào)用完成 string returnstring = caller.EndInvoke(result); // 通過獲得GUI線程的同步上下文的派生對象, // 然后調(diào)用Post方法來使更新GUI操作方法由GUI 線程去執(zhí)行 sc.Post(ShowState,returnstring); } // 顯示結(jié)果到richTextBox // 因?yàn)樵摲椒ㄊ怯蒅UI線程執(zhí)行的,所以當(dāng)然就可以訪問窗體控件了 private void ShowState(object result) { rtbState.Text = result.ToString(); btnDownLoad.Enabled = true; } }
程序的運(yùn)行結(jié)果和前面使用第一方案的結(jié)果是一樣的,這里就不重復(fù)貼圖了,上面所有的實(shí)現(xiàn)都是部分代碼,你可以在文章的最后下載本專題的所有源碼。
五、小結(jié)
到這里本專題關(guān)于異步編程模型的介紹就結(jié)束了,異步編程模型(APM)雖然是.NET 1.0中提出來的一個(gè)模式,相對于現(xiàn)在來說是舊了點(diǎn),并且微軟現(xiàn)在官方也表明在最新的代碼中不推薦使用該模型來實(shí)現(xiàn)異步的應(yīng)用程序,而是推薦使用基于任務(wù)的異步編程模型來實(shí)現(xiàn)異步的應(yīng)用程序,但是我個(gè)人認(rèn)為,正是因?yàn)樗?NET 1.0中提出的來,并且現(xiàn)在來看確實(shí)有些舊了, 所以我們才更應(yīng)該好好研究下它,因?yàn)楹竺嫣岢龅腅AP和TAP微軟做了更多的封裝,是我們對異步編程的本質(zhì)都不清楚的(其實(shí)它們的本質(zhì)都是使用線程池和委托機(jī)制的,具體可以查看前面的相關(guān)部分),并且系統(tǒng)學(xué)習(xí)下異步編程,也可以讓我們對新的異步編程模型的所帶來的好處有更可直觀的認(rèn)識。在后面的一專題我將帶大家全面認(rèn)識下基于事件的異步編程模型(EAP)。
附件:http://down.51cto.com/data/2362807創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國云服務(wù)器,動態(tài)BGP最優(yōu)骨干路由自動選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動現(xiàn)已開啟,新人活動云服務(wù)器買多久送多久。