程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [你必須知道的異步編程]——異步編程模型(APM)

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

編輯:C#入門知識

本專題概要:

  • 引言
  • 你知道APM嗎?
  • 你想知道如何使用異步編程模型編寫代碼嗎?
  • 使用委托也可以實現異步編程,你知道否?
  • 小結

 

一、引言

  在前面的C#基礎知識系列中介紹了從C#1.0——C#4.0中一些主要特性,然而.NET 4.5更新,除了提供了一些新的類和一些新的模板外,對於C#語言也做了一定的更新,最重要的就是.NET 4.5(對應於C#5.0)中提供了async和await兩個關鍵字,這兩個關鍵字是我們實現異步編程更加容易了,其實早在.NET 1.0開始微軟就對異步編程做了相應的支持——即異步編程模型(APM), 之後在.NET 2.0中又提出了基於事件的異步編程模型(EAP),.NET 4.0中又提出了基於任務的異步編程模型(TAP)。所以為了幫助大家全面理解.NET類庫對異步編程的支持,這裡我把我學習異步編程的一些體會和理解分享出來,希望對大家在學習的過程中有所幫助。

  在開始講解APM之前,我想先分享一下Visual Studio 版本、C# 版本和.NET 版本的一個對應關系。之所以在這裡分享這個對應關系,是因為在C#基礎知識系列的文章發布之後,有些初學者對.NET版本和C#語言特性之間的對應關系有點不清楚,有時候會弄混淆了。並且通過這個對應關系,也可以幫助大家對C#和.NET 類庫有個全面的把控,可以幫助大家理清楚C#和.NET 類庫中各個知識點,使他們可以對號入坐。具體他們的之間對應關系見下表:

Linq

  APM即異步編程模型的簡寫(Asynchronous Programming Model),大家在寫代碼的時候或者查看.NET 的類庫的時候肯定會經常看到和使用以BeginXXX和EndXXX類似的方法,其實你在使用這些方法的時候,你就再使用異步編程模型來編寫程序。異步編寫模型是一種模式,該模式允許用更少的線程去做更多的操作,.NET Framework很多類也實現了該模式,同時我們也可以自定義類來實現該模式,(也就是在自定義的類中實現返回類型為IAsyncResult接口的BeginXXX方法和EndXXX方法)另外委托類型也定義了BeginInvoke和EndInvoke方法,並且我們使用WSDL.exe和SvcUtil.exe工具來生成Web服務的代理類型時,也會生成使用了APM的BeginXxx和EndXxx方法。下面就具體就拿FileStream類的BeginReadEndRead方法來介紹下下異步編程模型的實現。

BeginXxx方法——開始執行異步操作介紹

當需要讀取文件中的內容時,我們通常會采用FileStream的同步方法Read來讀取,該同步方法的定義為:

   Read([] array,  offset,  count )

BeginRead方法代表異步執行Read操作,並返回實現IAsyncResult接口的對象,該對象存儲著異步操作的信息,下面就看下BeginRead方法的定義,看看與同步Read的方法區別在哪裡的.

  IAsyncResult BeginRead([] array,  offset, 

  從上面的代碼中可以看出異步方法和同步方法的區別,如果你在使用該異步方法時,不希望異步操作完成後調用任何代碼,你可以把userCallback參數設置為null。該異步方法子所以不會堵塞UI線程是因為調用該方法後,該方法會立即把控制權返回給調用線程(如果是UI線程來調用該方法時,即返回給UI線程),然而同步卻不是這樣,同步方法是等該操作完成之後返回讀取的內容之後才返回給調用線程,從而導致在操作完成之前調用線程就一直等待狀態。

EndXxx方法——結束異步操作介紹

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

對於訪問異步操作的結果,APM提供了四種方式供開發人員選擇:

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

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

既然這裡講到了FileStream對象,這裡就提出一個關於該類值得注意的地方的:

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

同樣道理,當創建的FileStream對象指定了FileOptions.Asynchronous參數時,然後我們仍然可以調用Read同步方法,此時在內部,FileStream類會開始一個異步操作,並立即使調用線程進入睡眠狀態,知道操作完成才會喚醒,通過這樣來模擬同步行為。因此在使用FileStream對象時,需要先決定是同步執行還是異步執行。並顯示地指定FileOptions.Asynchronous參數或useAsync參數。

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

  介紹了這麼久的異步編程模型,大家肯定很迫不及待地想使用異步編程模型來改寫自己的同步應用程序或者實現一個異步的應用程序。下面就通過一個例子來演示如何使用APM來現異步編程(該程序也實現了一個同步方法,為了讓大家更好地體會同步線程和異步線程的區別,本程序的實現是一個控制台程序,大家也可以很好地一直與WinForm應用程序和WPF程序):

   use APM to download file asynchronously

           DownloadFileAsync(
                HttpWebRequest myHttpWebRequest =
                RequestState requestState = =
          
            RequestState myRequestState ==
            myRequestState.response =
            Stream responseStream === responseStream.BeginRead(myRequestState.BufferRead, 
          
                RequestState myRequestState =
                Stream responserStream = 
                 readSize = (readSize > 

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

如果我們調用的是同步方法時,此時會堵塞主線程,直到文件的下載操作被完成之後主線程才繼續執行後面的代碼,下面是下載文件的同步方法:

  Download File Synchrously
           DownLoadFileSync(
            RequestState requestState=
                HttpWebRequest myHttpWebRequest =
                requestState.request === readSize = requestState.streamResponse.Read(requestState.BufferRead,  (readSize > = requestState.streamResponse.Read(requestState.BufferRead,  

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

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

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

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

AsyncMethodCaller(= CheckForIllegalCrossThreadCalls = btnDownLoad_Click(= (txbUrl.Text == = DownLoadFileSync( RequestState requestState = HttpWebRequest myHttpWebRequest = requestState.request === readSize = requestState.streamResponse.Read(requestState.BufferRead, (readSize > = requestState.streamResponse.Read(requestState.BufferRead, .Format(, requestState.filestream.Length) + .Format( .Format( = returnstring= rtbState.Text =

運行的結果為:

4.2 在線程中訪問另一個線程創建的控件

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

   
           AsyncMethodCaller(
           ShowStateDelegate(= =   btnDownLoad_Click(= =  (txbUrl.Text == = 
            sc =
          DownLoadFileSync(
            RequestState requestState = 
                HttpWebRequest myHttpWebRequest =
                requestState.request === readSize = requestState.streamResponse.Read(requestState.BufferRead,  (readSize > = requestState.streamResponse.Read(requestState.BufferRead, 
                 .Format(, requestState.filestream.Length) + .Format( .Format(
         =
             returnstring =

          ShowState(== 

程序的運行結果和前面使用第一方案的結果是一樣的,這裡就不重復貼圖了,上面所有的實現都是部分代碼,你可以在文章的最後下載本專題的所有源碼。

五、小結

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

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved