異步調(diào)用機(jī)制及實(shí)現(xiàn)方法_第1頁
異步調(diào)用機(jī)制及實(shí)現(xiàn)方法_第2頁
異步調(diào)用機(jī)制及實(shí)現(xiàn)方法_第3頁
異步調(diào)用機(jī)制及實(shí)現(xiàn)方法_第4頁
異步調(diào)用機(jī)制及實(shí)現(xiàn)方法_第5頁
已閱讀5頁,還剩5頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、這篇文章將介紹異步調(diào)用的實(shí)現(xiàn)機(jī)制及如何調(diào)用異步方法。大多數(shù).NET開發(fā)者在經(jīng)過delegate、Thread、AsynchronousInvocation之后,通常都會(huì)對(duì)以上概念產(chǎn)生混淆及誤用。實(shí)際上,以上概念是.NET2.0版本中對(duì)并行編程的核心支持,基于概念上的錯(cuò)誤認(rèn)識(shí)有可能導(dǎo)致在實(shí)際的編程中,無法利用異步調(diào)用的特性優(yōu)化我們的程序,例如大數(shù)據(jù)量加載引起的窗體”假死”。事實(shí)上這并不是一個(gè)困難的問題,該文將以一種逐層深入、抽絲剝繭的方式逐漸深入到異步編程的學(xué)習(xí)中。同步與異步大多數(shù)人并不喜歡閱讀大量的文字說明,而喜歡直接閱讀代碼,因此,我們?cè)谙挛闹袑⒅饕源a的形式闡述同步與異步的調(diào)用。同步方法

2、調(diào)用假設(shè)我們有一個(gè)函數(shù),它的功能是將當(dāng)前線程掛起3秒鐘。static void Sleep()Thread.Sleep(3000);通常,當(dāng)你的程序在調(diào)用Sleep后,它將等待3秒鐘的時(shí)間,在這3秒鐘時(shí)間內(nèi),你不能做任何其他操作。3秒之后,控制權(quán)被交回給調(diào)用線程(通常也就是你的主線程,即WinForm程序的UI線程)。這種類型的調(diào)用稱為同步,本次調(diào)用順序如下: 調(diào)用Sleep(); Sleep()執(zhí)行中; Sleep()執(zhí)行完畢,控制權(quán)歸還調(diào)用線程。我們?cè)俅握{(diào)用Sleep()函數(shù),不同的是,我們要基于委托來完成這次調(diào)用。一般為了將函數(shù)綁定在委托中,我們要定義與函數(shù)返回類型、參數(shù)值完全一致的委托

3、,這稍有點(diǎn)麻煩。但.NET內(nèi)部已經(jīng)為我們定義好了一些委托,例如MethodInvoker,這是一種無返回值、無參數(shù)的委托簽名,這相當(dāng)于你自定義了一種委托:public delegate void SimpleHandler();執(zhí)行以下代碼:MethodInvoker invoker = new MethodInvoker(Sleep);invoker.Invoke();我們使用了委托,但依然是同步的方式。主線程仍然要等待3秒的掛起,然后得到響應(yīng)。注意:Delegate.Invoke是同步方式的。異步方法調(diào)用如何在調(diào)用Sleep()方法的同時(shí),使主線程可以不必等待Sleep()的完成,一直能夠

4、得到相應(yīng)呢?這很重要,它意味著在函數(shù)執(zhí)行的同時(shí),主線程依然是非阻塞狀態(tài)。在后臺(tái)服務(wù)類型的程序中,非阻塞的狀態(tài)意味著該應(yīng)用服務(wù)可以在等待一項(xiàng)任務(wù)的同時(shí)去接受另一項(xiàng)任務(wù);在傳統(tǒng)的WinForm程序中,意味著主線程(即UI線程)依然可以對(duì)用戶的操作得到響應(yīng),避免了”假死”。我們繼續(xù)調(diào)用Sleep()函數(shù),但這次要引入BeginInvoke。MethodInvoker invoker = new MethodInvoker(Sleep);invoker.BeginInvoke(null, null); 注意BeginInvoke這行代碼,它會(huì)執(zhí)行委托所調(diào)用的函數(shù)體。同時(shí),調(diào)用BeginInvoke方法

5、的線程(以下簡稱為調(diào)用線程)會(huì)立即得到響應(yīng),而不必等待Sleep()函數(shù) 的完成。 以上代碼是異步的,調(diào)用線程完全可以在調(diào)用函數(shù)的同時(shí)處理其他工作,但是不足的是我們?nèi)匀徊恢缹?duì)于Sleep()函數(shù)的調(diào)用何時(shí)會(huì)結(jié)束,這是下文將要解決的問 題。 eginInvoke可以以異步的方式完全取代Invoke,我們也不必?fù)?dān)心函數(shù)包含參數(shù)的情況,下文介紹傳值問題。注意:Delegate.BeginInvoke是異步方式的。如果你要執(zhí)行一項(xiàng)任務(wù),但并不關(guān)心它何時(shí)完成,我們就可以使用BeginInvoke,它不會(huì)帶來調(diào)用線程的阻塞。對(duì)于異步調(diào)用,.NET內(nèi)部究竟做了什么?一旦你使用.NET完成了一次異步調(diào)用,它

6、都需要一個(gè)線程來處理異步工作內(nèi)容(以下簡稱異步線程),異步線程不可能是當(dāng)前的調(diào)用線程,因?yàn)槟菢尤匀粫?huì)造成調(diào)用線程的阻塞,與同步無異。事實(shí)上,.NET會(huì)將所有的異步請(qǐng)求隊(duì)列加入線程池,以線程池內(nèi)的線程處理所有的異步請(qǐng)求。對(duì)于線程池似乎不必了解的過于深入,但我們?nèi)孕枰P(guān)注以下幾點(diǎn)內(nèi)容: Sleep()的異步調(diào)用會(huì)在一個(gè)單獨(dú)的線程內(nèi)執(zhí)行,這個(gè)線程來自于.NET線程池。 .NET線程池默認(rèn)包含25個(gè)線程,你可以改變這個(gè)值的上限,每次異步調(diào)用都會(huì)使用其中某個(gè)線程執(zhí)行,但我們并不能控制具體使用哪一個(gè)線程。 線程池具備最大線程數(shù)目上限,一旦所有的線程都處于忙碌狀態(tài),那么新的異步調(diào)用將會(huì)被置于等待隊(duì)列,直到線

7、程池產(chǎn)生了新的可用線程,因此對(duì)于大量異步請(qǐng) 求,我們有必要關(guān)注請(qǐng)求數(shù)量,否則可能造成性能上的影響。簡單了解線程池為了暴露線程池的上限,我們修改Sleep()函數(shù),將線程掛起的時(shí)間延長至30s。在代碼的運(yùn)行輸出結(jié)果中,我們需要關(guān)注以下內(nèi)容: 線程池內(nèi)的可用線程數(shù)量。 異步線程是否來自于線程池。 線程托管ID值。上文已經(jīng)提到,.NET線程池默認(rèn)包含25個(gè)線程,因此我們連續(xù)調(diào)用30次異步方法,這樣可以在第25次調(diào)用后,看看線程池內(nèi)部究竟發(fā)生了什么。private void Sleep()int intAvailableThreads, intAvailableIoAsynThreds;/ 取得線程池

8、內(nèi)的可用線程數(shù)目,我們只關(guān)心第一個(gè)參數(shù)即可ThreadPool.GetAvailableThreads(out intAvailableThreads,out intAvailableIoAsynThreds);/ 線程信息string strMessage =String.Format("是否是線程池線程:0,線程托管ID:1,可用線程數(shù):2",Thread.CurrentThread.IsThreadPoolThread.ToString(),Thread.CurrentThread.GetHashCode(),intAvailableThreads);Console.

9、WriteLine(strMessage);Thread.Sleep(30000);private void CallAsyncSleep30Times()/ 創(chuàng)建包含Sleep函數(shù)的委托對(duì)象MethodInvoker invoker = new MethodInvoker(Sleep);for (int i = 0; i < 30; i+)/ 以異步的形式,調(diào)用Sleep函數(shù)30次invoker.BeginInvoke(null, null);輸出結(jié)果:對(duì)于輸出結(jié)果,我們可以總結(jié)為以下內(nèi)容: 所有的異步線程都來自于.NET線程池。 每次執(zhí)行一次異步調(diào)用,便產(chǎn)生一個(gè)新的線程;同時(shí)可用線程

10、數(shù)目減少。 在執(zhí)行異步調(diào)用25次后,線程池中不再有空閑線程。此時(shí),應(yīng)用程序會(huì)等待空閑線程的產(chǎn)生。 一旦線程池內(nèi)產(chǎn)生了空閑線程,它會(huì)立即被分配給異步任務(wù)等待隊(duì)列,之后線程池中仍然不具備空閑線程,應(yīng)用程序主線程進(jìn)入掛起狀態(tài)繼續(xù)等待空閑線程,這樣 的調(diào)用一直持續(xù)到異步調(diào)用被執(zhí)行完30次。針對(duì)以上結(jié)果,我們對(duì)于異步調(diào)用可以總結(jié)為以下內(nèi)容: 每次異步調(diào)用都在新的線程中執(zhí)行,這個(gè)線程來自于.NET線程池。 線程池有自己的執(zhí)行上限,如果你想要執(zhí)行多次耗費(fèi)時(shí)間較長的異步調(diào)用,那么線程池有可能進(jìn)入一種”線程饑餓”狀態(tài),去等待可用線程的產(chǎn)生。BeginInvoke和EndInvoke我們已經(jīng)知道,如何在不阻塞調(diào)用

11、線程的情況下執(zhí)行一個(gè)異步調(diào)用,但我們無法得知異步調(diào)用的執(zhí)行結(jié)果,及它何時(shí)執(zhí)行完畢。為了解決以上問題,我們可以使用EndInvoke。EndInvoke在異步方法執(zhí)行完成前,都會(huì)造成線程的阻塞。因此,在調(diào)用BeginInvoke之后調(diào)用EndInvoke,效果幾乎完全等同于以阻塞模式執(zhí)行你的函數(shù)(EndInvoke會(huì)使調(diào)用線程掛起,一直到異步函數(shù)執(zhí)行完畢)。但是,.NET是如何將BeginInvoke和EndInvoke進(jìn)行綁定呢?答案就是IAsyncResult。每次我們使用BeginInvoke,返回值都是IAsyncResult類型,它是.NET追蹤異步調(diào)用的關(guān)鍵值。每次異步調(diào)用之后的結(jié)果

12、如何?如果要了解具體執(zhí)行結(jié)果,IAsyncResult便可視為一個(gè)標(biāo)簽。通過這個(gè)標(biāo)簽,你可以了解異步調(diào)用何時(shí)執(zhí)行完畢,更重要的是,它可以保存異步調(diào)用的參數(shù)傳值,解決異步函數(shù)上下文問題。我們現(xiàn)在通過幾個(gè)例子來了解IAsyncResult。如果之前對(duì)它了解不多,那么就需要耐心的將它領(lǐng)悟,因?yàn)檫@種類型的調(diào)用是.NET異步調(diào)用的關(guān)鍵內(nèi)容。private void SleepOneSecond()/ 當(dāng)前線程掛起1秒Thread.Sleep(1000);private void UsingEndInvoke()/ 創(chuàng)建一個(gè)指向SleepOneSecond的委托MethodInvoker invoker

13、= new MethodInvoker(SleepOneSecond);/ 開始執(zhí)行SleepOneSecond,但這次異步調(diào)用我們傳遞一些參數(shù)/ 觀察Delegate.BeginInvoke()的第二個(gè)參數(shù)IAsyncResult tag = invoker.BeginInvoke(null, "passing some state");/ 應(yīng)用程序在此處會(huì)造成阻塞,直到SleepOneSecond執(zhí)行完成invoker.EndInvoke(tag);/ EndInvoke執(zhí)行完畢,取得之前傳遞的參數(shù)內(nèi)容string strState = (string)tag.Asyn

14、cState;Console.WriteLine("EndInvoke的傳遞參數(shù)" + tag.AsyncState.ToString();輸出結(jié)果: 回到文章初始提到的”窗體動(dòng)態(tài)更新”問題,如果你將上述代碼運(yùn)行在一個(gè)WinForm程序中,會(huì)發(fā)現(xiàn)窗體依然陷入”假死”。對(duì)于這種情況,你可能會(huì)陷入疑惑:之前說異步函數(shù)都執(zhí)行在線程池中,因此可以肯定異步函數(shù)的執(zhí)行不會(huì)引起UI線程的忙碌,但為什么窗體依然陷入了”假死”?問題就在于EndInvoke。EndInvoke此時(shí)扮演的角色就是”線程鎖”,它充當(dāng)了一個(gè)調(diào)用線程與異步線程之間的調(diào)度器,有時(shí)調(diào)用線程需要使用異步函數(shù)的執(zhí)行結(jié)果,那么

15、調(diào)度線程就需要在異步執(zhí)行完之前一直等待,直到得到結(jié)果方可繼續(xù)運(yùn)行。EndInvoke一方面負(fù)責(zé)監(jiān)聽異步函數(shù)的執(zhí)行狀況,一方面將調(diào)用線程掛起。因此在Win Form環(huán)境下,UI線程的”假死”并不是因?yàn)榫€程忙碌造成,而是被EndInvoke”善意的”暫時(shí)封鎖,它只是為了等待異步函數(shù)的完成。我們可以對(duì)EndInvoke總結(jié)如下: 在執(zhí)行EndInvoke時(shí),調(diào)用線程會(huì)進(jìn)入掛起狀態(tài),一直到異步函數(shù)執(zhí)行完成。 使用EndInvoke可以使應(yīng)用程序得知異步函數(shù)何時(shí)執(zhí)行完畢。 如果將上述寫法稱為”異步”,你一定覺得這種”異步”徒具其名,雖然知道異步函數(shù)何時(shí)執(zhí)行完畢,也得到了異步函數(shù)的傳值,但我們的調(diào)用線程仍

16、然會(huì)等待函數(shù)執(zhí)行完畢,在等待過程中線程阻塞,實(shí)際上與同步調(diào)用無異。如何捕捉異常?現(xiàn)在我們把問題稍微復(fù)雜化,考慮異步函數(shù)拋出異常的一種情形。我們需要了解在何處捕捉到異常,是BeginInvoke,還是EndInvoke?甚至是有沒有可能無法捕捉異常?答案是EndInvoke。BeginInvoke的工作只是開始線程池對(duì)于異步函數(shù)的執(zhí)行工作,EndInvoke則需要處理函數(shù)執(zhí)行完成的所有信息,包括其中產(chǎn)生的異常。private void SleepOneSecond()Thread.Sleep(3000);throw new Exception("Here Is An Async Fun

17、ction Exception");private void UsingEndInvoke()/ 創(chuàng)建一個(gè)指向SleepOneSecond的委托MethodInvoker invoker = new MethodInvoker(SleepOneSecond);/ 開始執(zhí)行SleepOneSecond,但這次異步調(diào)用我們傳遞一些參數(shù)/ 觀察Delegate.BeginInvoke()的第二個(gè)參數(shù)IAsyncResult tag = invoker.BeginInvoke(null, "passing some state");try/ 應(yīng)用程序在此處會(huì)造成阻塞,直到

18、SleepOneSecond執(zhí)行完成invoker.EndInvoke(tag);catch (Exception ex)/ 此處可以捕捉異常MessageBox.Show(ex.Message);/ EndInvoke執(zhí)行完畢,取得之前傳遞的參數(shù)內(nèi)容string strState = (string)tag.AsyncState;Console.WriteLine("EndInvoke的傳遞參數(shù)" + tag.AsyncState.ToString();執(zhí)行以上代碼后,你將發(fā)現(xiàn)只有在使用EndInvoke時(shí),才會(huì)捕捉到異常,否則異常將丟失。需要注意的是,直接在編譯器中運(yùn)行

19、程序是無法產(chǎn)生捕獲異常的,只有在Debug、Release環(huán)境下運(yùn)行,異常才會(huì)以對(duì)話框的形式直接彈出。向函數(shù)中傳遞參數(shù)現(xiàn)在我們來改變一下異步函數(shù),讓它接收一些參數(shù)。private string FuncWithParameters(int param1, string param2, ArrayList param3)/ 我們?cè)谶@里改變參數(shù)值param1 = 100;param2 = "hello"param3 = new ArrayList();return "thank you for reading me"下面我們使用BeginInvoke與End

20、Invoke來調(diào)用這個(gè)函數(shù),首先,我們創(chuàng)建一個(gè)匹配該函數(shù)的委托簽名。public delegate string DelegateWithParameters(int param1, string param2, ArrayList param3);我們可以將BeginInvoke和EndInvoke視為將異步函數(shù)分割為兩部分的特殊函數(shù)。BeginInvoke通過自己的兩個(gè)參數(shù)值(一個(gè)AsyncCallBack委托,一個(gè)object對(duì)象)來接收傳入?yún)?shù),EndInvoke用于計(jì)算傳出參數(shù)(標(biāo)記了out或者ref的參數(shù))和函數(shù)返回值?,F(xiàn)在我們回到自己的函數(shù)FuncWithParameters,p

21、aram1、param2、param3是傳入值,同時(shí),它們也作為BeginInvoke的參數(shù)來處理;函數(shù)的返回值是string類型,它將作為EndInvoke的返回類型。比較酷的是,編譯器可以通過委托類型,來自動(dòng)為BeginInvoke和EndInvoke生成正確的參數(shù)與返回值類型。注意我們?cè)诋惒胶瘮?shù)中為參數(shù)分配了新的值,這樣可以檢驗(yàn)這些參數(shù)在調(diào)用異步函數(shù)后,究竟會(huì)傳出什么樣的值private void CallFuncWithParameters()/ 創(chuàng)建幾個(gè)參數(shù)string strParam = "Param1"int intValue = 100;ArrayList

22、 list = new ArrayList();list.Add("Item1");/ 創(chuàng)建委托對(duì)象DelegateWithParameters delFoo =new DelegateWithParameters(FuncWithParameters);/ 調(diào)用異步函數(shù)IAsyncResult tag =delFoo.BeginInvoke(intValue, strParam, list, null, null);/ 通常調(diào)用線程會(huì)立即得到響應(yīng)/ 因此你可以在這里進(jìn)行一些其他處理/ 執(zhí)行EndInvoke來取得返回值string strResult = delFoo.E

23、ndInvoke(tag);Trace.WriteLine("param1: " + intValue);Trace.WriteLine("param2: " + strParam);Trace.WriteLine("ArrayList count: " + list.Count);我們的異步函數(shù)對(duì)參數(shù)的改變并沒有影響其傳出值,現(xiàn)在我們把ArrayList變?yōu)閞ef參數(shù),看看會(huì)給EndInvoke帶來什么變化。public delegate string DelegateWithParameters(out int param1, string param2, ref ArrayList param3);private string FuncWithParameters(out int param1, string param2, ref ArrayList param3)/ 我們?cè)谶@里改變參數(shù)值param1 = 300;param2 = "hello"param3 = new ArrayList();return "thank you for reading me"private void CallFuncWithParamet

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論