溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

[你必須知道的異步編程]——異步編程模型(APM)

發(fā)布時間:2020-07-26 08:47:36 來源:網(wǎng)絡(luò) 閱讀:1311 作者:LearningHard 欄目:編程語言

本專題概要:

  • 引言

  • 你知道APM嗎?

  • 你想知道如何使用異步編程模型編寫代碼嗎?

  • 使用委托也可以實現(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兩個關(guān)鍵字,這兩個關(guān)鍵字是我們實現(xiàn)異步編程更加容易了,其實早在.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 版本的一個對應(yīng)關(guān)系。之所以在這里分享這個對應(yīng)關(guān)系,是因為在C#基礎(chǔ)知識系列的文章發(fā)布之后,有些初學(xué)者對.NET版本和C#語言特性之間的對應(yīng)關(guān)系有點不清楚,有時候會弄混淆了。并且通過這個對應(yīng)關(guān)系,也可以幫助大家對C#和.NET 類庫有個全面的把控,可以幫助大家理清楚C#和.NET 類庫中各個知識點,使他們可以對號入坐。具體他們的之間對應(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

隱式類型的部變量

對象集合初始化

自動實現(xiàn)屬性

匿名類型

擴展方法

查詢表達式

Lambda表達式

表達式樹

分部類和方法

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),大家在寫代碼的時候或者查看.NET 的類庫的時候肯定會經(jīng)??吹胶褪褂靡訠eginXXX和EndXXX類似的方法,其實你在使用這些方法的時候,你就再使用異步編程模型來編寫程序。異步編寫模型是一種模式,該模式允許用更少的線程去做更多的操作,.NET Framework很多類也實現(xiàn)了該模式,同時我們也可以自定義類來實現(xiàn)該模式,(也就是在自定義的類中實現(xiàn)返回類型為IAsyncResult接口的BeginXXX方法和EndXXX方法),另外委托類型也定義了BeginInvoke和EndInvoke方法,并且我們使用WSDL.exe和SvcUtil.exe工具來生成Web服務(wù)的代理類型時,也會生成使用了APM的BeginXxx和EndXxx方法。下面就具體就拿FileStream類的BeginReadEndRead方法來介紹下下異步編程模型的實現(xiàn)。

BeginXxx方法——開始執(zhí)行異步操作介紹

當需要讀取文件中的內(nèi)容時,我們通常會采用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í)行的線程,當一個WinForm程序需要實現(xiàn)讀取一個大文件的內(nèi)容然后把內(nèi)容顯示在界面時,如果我們調(diào)用該方法去讀取文件的內(nèi)容時,此時Read方法會堵塞UI線程,在讀取文件內(nèi)容沒有完成之前,用戶不能對窗體進行任何的操作,包括關(guān)閉應(yīng)用程序,此時用戶看到的該窗體會出現(xiàn)無法響應(yīng),這樣就給用戶帶來不好一個用戶體驗,從用戶角度來看是用戶體驗不好,此時我們自己解決問題的思路肯定是——能不能讓讀取文件操作在另外一個線程中執(zhí)行,這樣就不會堵塞UI線程,這時候UI線程繼續(xù)做屬于自己的事情,即響應(yīng)用戶的操作。不錯,微軟也肯定也想到了這個解決方案的,并且在實際操作中也是這么做的,即通過BeginRead方法來實現(xiàn)異步編程,使讀取操作不再堵塞UI線程。BeginRead方法代表異步執(zhí)行Read操作,并返回實現(xiàn)IAsyncResult接口的對象,該對象存儲著異步操作的信息,下面就看下BeginRead方法的定義,看看與同步Read的方法區(qū)別在哪里的.

// 開始異步讀操作
// 前面的3個參數(shù)和同步方法代表的意思一樣,這里就不說了,可以看到這里多出了2個參數(shù)
// userCallback代表當異步IO操作完成時,你希望由一個線程池線程執(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ū)別,如果你在使用該異步方法時,不希望異步操作完成后調(diào)用任何代碼,你可以把userCallback參數(shù)設(shè)置為null。該異步方法子所以不會堵塞UI線程是因為調(diào)用該方法后,該方法會立即把控制權(quán)返回給調(diào)用線程(如果是UI線程來調(diào)用該方法時,即返回給UI線程),然而同步卻不是這樣,同步方法是等該操作完成之后返回讀取的內(nèi)容之后才返回給調(diào)用線程,從而導(dǎo)致在操作完成之前調(diào)用線程就一直等待狀態(tài)。

EndXxx方法——結(jié)束異步操作介紹

  前面介紹完了BeginXxx方法,我們看到所有BeginXxx方法返回的都是實現(xiàn)了IAsyncResult接口的一個對象,并不是對應(yīng)的同步方法所要得到的結(jié)果的。此時我們需要調(diào)用對應(yīng)的EndXxx方法來結(jié)束異步操作,并向該方法傳遞IAsyncResult對象,EndXxx方法的返回類型就是和同步方法一樣的。例如,FileStreamEndRead方法返回一個Int32來代表從文件流中實際讀取的字節(jié)數(shù)。

對于訪問異步操作的結(jié)果,APM提供了四種方式供開發(fā)人員選擇:

  1. 在調(diào)用BeginXxx方法的線程上調(diào)用EndXxx方法來得到異步操作的結(jié)果,但是這種方式會阻塞調(diào)用線程,知道操作完成之后調(diào)用線程才繼續(xù)運行

  2. 查詢IAsyncResultAsyncWaitHandle屬性,從而得到WaitHandle,然后再調(diào)用它的WaitOne方法來使一個線程阻塞并等待操作完成再調(diào)用EndXxx方法來獲得操作的結(jié)果。

  3. 循環(huán)查詢IAsyncResultIsComplete屬性,操作完成后再調(diào)用EndXxx方法來獲得操作返回的結(jié)果。

  4. 使用 AsyncCallback委托來指定操作完成時要調(diào)用的方法,在操作完成后調(diào)用的方法中調(diào)用EndXxx操作來獲得異步操作的結(jié)果。

  在上面的4種方式中,第4種方式是APM的首選方式,因為此時不會阻塞執(zhí)行BeginXxx方法的線程,然而其他三種都會阻塞調(diào)用線程,相當于效果和使用同步方法是一樣,個人感覺根本失去了異步編程的特點,所以其他三種方式可以簡單了解下,在實際異步編程中都是使用委托的方式。

  通過上面的介紹,大家應(yīng)該對異步編程模型有了進一步的了解了吧,要識別某個類是否實現(xiàn)了異步編程模型,只需要看是不是有BeginXxx方法(當然返回類型需要是IAsyncResult)和EndXxx方法。其實異步編程模型這個模式,就是微軟利用委托和線程池幫助我們實現(xiàn)的一個模式(該模式利用一個線程池線程去執(zhí)行一個操作,在FileStream類BeginRead方法中就是執(zhí)行一個讀取文件操作,該線程池線程會立即將控制權(quán)返回給調(diào)用線程,此時線程池線程在后臺進行這個異步操作;異步操作完成之后,通過回調(diào)函數(shù)來獲取異步操作返回的結(jié)果。此時就是利用委托的機制。所以說異步編程模式時利用委托和線程池線程搞出來的模式,包括后面的基于事件的異步編程和基于任務(wù)的異步編程,還有C# 5中的async和await關(guān)鍵字,都是利用這委托和線程池搞出來的。他們的本質(zhì)其實都是一樣的,只是后面提出來的使異步編程更加簡單罷了。)

既然這里講到了FileStream對象,這里就提出一個關(guān)于該類值得注意的地方的:

FileStream對象默認情況下是同步打開操作系統(tǒng)句柄,當我們創(chuàng)建一個FileStream對象沒有為其指定FileOptions.Asynchronous參數(shù)或者沒有顯示指定useAsync為true時,Windows 操作系統(tǒng)會以同步的方法執(zhí)行所有的文件操作,即使此時你還是可以調(diào)用BeginRead方法。但是這樣對于你的應(yīng)用程序,操作只是表面上是異步執(zhí)行的,但FileStream類在內(nèi)部會用另一個線程模擬異步行為。

同樣道理,當創(chuàng)建的FileStream對象指定了FileOptions.Asynchronous參數(shù)時,然后我們?nèi)匀豢梢哉{(diào)用Read同步方法,此時在內(nèi)部,F(xiàn)ileStream類會開始一個異步操作,并立即使調(diào)用線程進入睡眠狀態(tài),知道操作完成才會喚醒,通過這樣來模擬同步行為。因此在使用FileStream對象時,需要先決定是同步執(zhí)行還是異步執(zhí)行。并顯示地指定FileOptions.Asynchronous參數(shù)或useAsync參數(shù)。

三、你想知道如何使用異步編程模型編寫代碼嗎?

  介紹了這么久的異步編程模型,大家肯定很迫不及待地想使用異步編程模型來改寫自己的同步應(yīng)用程序或者實現(xiàn)一個異步的應(yīng)用程序。下面就通過一個例子來演示如何使用APM來現(xiàn)異步編程(該程序也實現(xiàn)了一個同步方法,為了讓大家更好地體會同步線程和異步線程的區(qū)別,本程序的實現(xiàn)是一個控制臺程序,大家也可以很好地一直與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

運行結(jié)果為(從運行結(jié)果也可以看出,在主線程中調(diào)用 DownloadFileAsync(downUrl)方法時,DownloadFileAsync(downUrl)方法中的myHttpWebRequest.BeginGetResponse調(diào)用被沒有阻塞調(diào)用線程(即主線程),而是立即返回到主線程,是主線程后面的代碼可以立即執(zhí)行)

[你必須知道的異步編程]——異步編程模型(APM)

如果我們調(diào)用的是同步方法時,此時會堵塞主線程,直到文件的下載操作被完成之后主線程才繼續(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

使用同步方法下載文件的運行結(jié)果為(大家可以對照兩個方式的結(jié)果就可以明顯看出他們的區(qū)別了。):

[你必須知道的異步編程]——異步編程模型(APM)

四、使用委托也可以實現(xiàn)異步編程,你知道否?

  在前面的介紹中已經(jīng)提到委托類型也會定義了BeginInvoke方法和EndInvoke方法,所以委托類型也實現(xiàn)了異步編程模型,所以可以使用委托的BeginInvokeEndInvoke方法來回調(diào)同步方法從而實現(xiàn)異步編程。因為調(diào)用委托的BeginInvoke方法來執(zhí)行一個同步方法時,此時會使用線程池線程回調(diào)這個同步方法并立即返回到調(diào)用線程中,由于耗時操作在另外一個線程上運行,所以執(zhí)行BeginInvoke方法的主線程就不會被堵塞。但是這里存在的一個問題時,因為同步方法在另外一個線程中執(zhí)行的,然而我們怎么把同步方法執(zhí)行的狀態(tài)反應(yīng)到UI界面上來呢?因為在GUI應(yīng)用程序(包括Windows窗體,WPF和Silverlight)中,創(chuàng)建窗口的線程是唯一能夠?qū)δ莻€窗口進行更新的線程,所以在執(zhí)行同步方法的線程就不能對窗口中的控件進行操作,也就不能把方法允許的結(jié)果反應(yīng)到窗體上了。這里有兩種解決方案,一種是設(shè)置控件的CheckForIllegalCrossThreadCalls 屬性為false,設(shè)置為false的意思代表允許跨線程調(diào)用,(這種方式雖然可以解決該問題,但是不推薦,因為它違背了.NET安全規(guī)范);第二種就是使用SynchronizationContext基類,該類記錄著線程的同步上下文對象,我們可以通過在GUI線程中調(diào)用SynchronizationContext.Current屬性來獲得GUI線程的同步上下文,然后當線程池線程需要更新窗體時,可以調(diào)用保存的SynchronizationContext派生對象的Post方法(Post方法會將回調(diào)函數(shù)送到GUI線程的隊列中,每個線程都有各自的操作隊列的,線程的執(zhí)行都是從這個隊列中拿方法去執(zhí)行),向Post方法傳遞要由GUI線程調(diào)用的方法(該方法的定義要匹配SendOrPostCallback委托的簽名),還需要想Post方法傳遞一個要傳給回調(diào)方法的參數(shù)。

4.1 使用委托實現(xiàn)更好的用戶體驗——不堵塞UI線程

雖然第一種方案是一種不推薦的方案,但是我覺得有些朋友還是不知道怎么實現(xiàn)的,所以在這部分就用具體的代碼來實現(xiàn)下,并且該實現(xiàn)也可以與使用同步上下文對象的方式進行對比,這樣大家就可以更加了解如何使用委托來進行異步編程了。下面就具體看實現(xiàn)代碼吧:

View Code
 // 定義用來實現(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)用
            // 實際開發(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);
        }
        // 同步下載文件的方法
        // 該方法會阻塞主線程,使用戶無法對界面進行操作
        // 在文件下載完成之前,用戶甚至都不能關(guā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控件的線程不是一個線程
                // 如果不把 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();
            }
        }
        // 異步操作完成時執(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;     
        }

運行的結(jié)果為:

[你必須知道的異步編程]——異步編程模型(APM)

4.2 在線程中訪問另一個線程創(chuàng)建的控件

這部分將使用同步上下文的方式來實現(xiàn)在線程池線程中如何更新GUI線程中窗體,因為在程序代碼部分都有詳細的解釋,這里就直接貼代碼了

public partial class MainForm : Form
    {
        // 定義用來實現(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;
        }
        // 同步下載文件的方法
        // 該方法會阻塞主線程,使用戶無法對界面進行操作
        // 在文件下載完成之前,用戶甚至都不能關(guā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控件的線程不是一個線程
                // 如果不把 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();
            }
        }
        // 異步操作完成時執(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
        // 因為該方法是由GUI線程執(zhí)行的,所以當然就可以訪問窗體控件了
        private void ShowState(object result)
        {
            rtbState.Text = result.ToString();
            btnDownLoad.Enabled = true;
        }
    }

程序的運行結(jié)果和前面使用第一方案的結(jié)果是一樣的,這里就不重復(fù)貼圖了,上面所有的實現(xiàn)都是部分代碼,你可以在文章的最后下載本專題的所有源碼。

五、小結(jié)

  到這里本專題關(guān)于異步編程模型的介紹就結(jié)束了,異步編程模型(APM)雖然是.NET 1.0中提出來的一個模式,相對于現(xiàn)在來說是舊了點,并且微軟現(xiàn)在官方也表明在最新的代碼中不推薦使用該模型來實現(xiàn)異步的應(yīng)用程序,而是推薦使用基于任務(wù)的異步編程模型來實現(xiàn)異步的應(yīng)用程序,但是我個人認為,正是因為它是.NET 1.0中提出的來,并且現(xiàn)在來看確實有些舊了, 所以我們才更應(yīng)該好好研究下它,因為后面提出的EAP和TAP微軟做了更多的封裝,是我們對異步編程的本質(zhì)都不清楚的(其實它們的本質(zhì)都是使用線程池和委托機制的,具體可以查看前面的相關(guān)部分),并且系統(tǒng)學(xué)習(xí)下異步編程,也可以讓我們對新的異步編程模型的所帶來的好處有更可直觀的認識。在后面的一專題我將帶大家全面認識下基于事件的異步編程模型(EAP)。


附件:http://down.51cto.com/data/2362807
向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI