單向操作特別適用于"觸發(fā)然后忘記"場(chǎng)景,在該場(chǎng)景中,客戶(hù)端程序并不期望服務(wù)回傳任何信息。但是,許多操作并不適用于這種情況,其向客戶(hù)端程序返回數據。為了處理這些情況,WCF支持異步操作和IAsyncResult設計模式。在WCF中你可以使用兩種方式實(shí)現IAsyncResult設計模式:在客戶(hù)端 程序中異步調用操作;或在WCF服務(wù)中實(shí)現異步操作。
IAsyncResult 設計模式并不是為WCF特別設計的,它在.NET Framework中被廣泛使用。更多詳細信息,可以查閱MSDN的"異步編程設計模式" http://msdn.microsoft.com/en-us/library/ms228969.aspx
在客戶(hù)端程序中異步調用操作
使用WCF,你可以生成一個(gè)特定版本的代理類(lèi),使用該代理類(lèi)客戶(hù)端程序可以異步地調用服務(wù)的操作。在使用svcutil命令行工具時(shí)指定"/aysnc"標記可以生成特定版本的代理類(lèi)。你也可以在添加服務(wù)引用向導時(shí),點(diǎn)擊高級按鈕,然后在服務(wù)引用設置對話(huà)框中選擇生成異步操作復選框以生成該特定版本的代理 類(lèi)。
一個(gè)異步代理類(lèi)為每個(gè)操作提供一對begin和end方法??蛻?hù)端程序可以調用begin方法以實(shí)例化該操作。Begin方法在發(fā)送完請求后立即返回,但是.NET Framework運行時(shí)會(huì )在客戶(hù)端創(chuàng )建一個(gè)新的線(xiàn)程用以等待請求的響應。當你調用begin方法,你需提供回調方法的名字。當服務(wù)完成操作并返回結果只客戶(hù)端 代理后,新線(xiàn)程中的回調方法開(kāi)始執行。你使用回調方法獲取服務(wù)的返回結果。你應該調用操作的end方法以表明你已處理完服務(wù)的響應。
理解隨后這點(diǎn)非常重要:若要服務(wù)支持上述異步編程,你并不需要采用任何方式修改該服務(wù)。實(shí)際上,服務(wù)自身都不必為一個(gè)WCF服務(wù);它可能是通過(guò)其他技術(shù)實(shí)現的服務(wù)。使操作對客戶(hù)端程序為異步操作的代碼封裝在客戶(hù)端的代理對象和.NET Framework運行時(shí)中。所有與線(xiàn)程相關(guān)的問(wèn)題均由運行在客戶(hù)端的WCF運行時(shí)中的代碼去處理。服務(wù)所關(guān)注的是其操作與之前章節中的練習一樣被同步地調 用。
在WCF服務(wù)中實(shí)現異步操作
使用WCF,你還可以在服務(wù)中實(shí)現異步操作。在這種情況下,服務(wù)提供自己的一對begin和end方法組成該操作??蛻?hù)端程序通過(guò)代理對象調用操作使用普通的操作名調用操作(不使用begin方法)。WCF運行時(shí)分配操作的請求至begin方法,因此客戶(hù)端程序不必關(guān)注服務(wù)是否采用異步方式實(shí)現該操作。
服務(wù)的開(kāi)發(fā)人員可以添加邏輯至begin方法以選擇操作應同步或地者異步地執行。比如,如果服務(wù)當前的負載比較小,那么可以使用同步地方式執行操作以使該操作可以盡快的完成。如果負載增加,服務(wù)可能選擇以采用異步的方式執行操作。通過(guò)這種方式實(shí)現操作不需要修改客戶(hù)端程序。如果該操作經(jīng)過(guò)很長(cháng)一段時(shí)間后才向 客戶(hù)端返回數據,那么你應該使用這種方式實(shí)現這些操作。
當定義服務(wù)的一個(gè)操作時(shí),在該操作上設置OperationContract特性類(lèi)的AysncPattern屬性為true,以指定該操作支持異步處理;然后提供一對遵循前面所述的名稱(chēng)轉化和簽名的方法以實(shí)現IAsyncResult設計模式。
在下面一組練習中,你將添加另外一個(gè)名為CalculateTotalValueOfStock操作至AdventureWorksAdmin服務(wù)。該操作的目的是確定AdventureWorks倉庫中所有存貨當前的總價(jià)值。該操作將耗費一段時(shí)間才會(huì )運算完成,因此你需要采用異步方法實(shí)現該操作。
練習:添加一個(gè)異步操作至AdventureWorksAdmin服務(wù)
1. 在Visual Studio中,打開(kāi)App_Code文件夾下的IService.cs文件。添加下面的操作至服務(wù)合約AdventureWorksAdmin
該操作包含兩個(gè)方法BeginCalculateTotalValueOfStock和EndCalculateTotalValueOfStock。它們一起組成單個(gè)名為CalculateTotalValueOfStock的異步操作。在該操作中你必須使用上述命名方式來(lái)命名這兩個(gè)方法,只有這樣客戶(hù)端代理對象 才可以正確地識別它們。你可以任意指定該操作begin方法所需的參數(在本例中,客戶(hù)端程序將傳遞一個(gè)字符串參數以識別操作的調用),但是該方法的最后兩個(gè)參數必須為一個(gè)AysncCallback對象,該對象將指向客戶(hù)端程序中的一個(gè)回調方法;以及另外一個(gè)為參數為object,它持有客戶(hù)端程序提供的狀 態(tài)信息。Begin方法的返回值必須為IAsyncResult。 End方法必須接收單個(gè)類(lèi)型為IAsyncResult的參數,但是其返回值應當對應操作的返回值。在本例中, CalculateTotalValueOfStock操作返回一個(gè)int類(lèi)型,其值為存貨總價(jià)值的計算結果。
該操作的另外一個(gè)重要部分是OperationContract特性類(lèi)的AsyncPattern屬性。你只需要對begin方法應用 OperationContract特性。當你生成該服務(wù)的元數據時(shí)(比如生成客戶(hù)端代理),該屬性將導致begin和end被認為是單個(gè)異步操作的實(shí) 現。
2. 在解決方案窗口,在A(yíng)pp_Code文件夾上點(diǎn)擊右鍵,然后選擇添加已存項目。添加AyncResult.cs文件,該文件位于D:\Works\Solutions\WCF\Step.by.Step\Chapter12目錄下。
3. 打開(kāi)AysncResult.cs文件并檢查其內容。它包含一個(gè)generic類(lèi),該類(lèi)的名字為AsyncResult,它實(shí)現了 IAsyncResult接口。該類(lèi)更詳細的討論超出本書(shū)的返回,但是AysncResult類(lèi)的目的在于為其他實(shí)現異步方法的類(lèi)提供同步方法和狀態(tài)信 息。
在本練習中,該類(lèi)的兩個(gè)重要成員是
- Data屬性可以讀取異步操作的返回的數據。在本列中,CalculateTotalValueOfStock操作將填充該屬性,并在執行end方法時(shí)返回AsyncResult至客戶(hù)端程序。
- AysncResult構造方法接收兩個(gè)參數,并將這兩個(gè)參數存貯在對應的兩個(gè)私有變量中。服務(wù)將使用synchronous參數指明是否同步地調用操作,stateData參數將指向傳入begin方法的最后一個(gè)參數對象(保存該對象非常重要,因為該對象必須返回值客戶(hù)端程序以確保該對象完成了處理)
4. 打開(kāi)App_Code文件夾的Service.cs文件。然后添加如下的代理至AdventureWorksAdmin類(lèi)
你將在后續添加的方法中使用該代理。
5. 添加如下的方法至AdventureWorksAdmin類(lèi)
上述方法工作的細節超出本書(shū)的討論范圍(嚴格地講,該方法與WCF沒(méi)有任何關(guān)系)。但是總的來(lái)說(shuō),該方法生成一個(gè)隨機數,如果該數為偶數,它將使用同步方式執行操作(模擬低負載場(chǎng)景);否則使用異步方式執行操作(模擬高負載場(chǎng)景)。在同步場(chǎng)景下,代碼將創(chuàng )建一個(gè)新的AsyncResult對象,線(xiàn)程休眠20 秒以模擬執行計算的時(shí)間,然后用數字55555555填充AysncResult對象。在異步場(chǎng)景中,代碼同樣創(chuàng )建一個(gè)新的AsyncResult對象,但會(huì )產(chǎn)生一個(gè)新的線(xiàn)程,該新線(xiàn)程在后臺休眠30秒。代碼并不填充AsyncResult對象因為當后臺休眠的線(xiàn)程蘇醒后將填充AysncResult對象。 在上述兩種情況下,代碼都會(huì )調用客戶(hù)端程序中的回調方法,傳遞AsyncResult對象為回調方法的參數??蛻?hù)端程序將從該對象中接收到計算的結果?;卣{方法方法將返回相同的AysncResult對象(這是IAsyncResult設計模式的要求)。
該方法還顯示消息對話(huà)框以幫助你追蹤方法的執行,并確定操作是同步還是異步的。
6. 添加end方法至AdventureWorksAdmin類(lèi)
當begin方法完成后調用上述end方法。該方法的目的是從傳入的IAsyncResult對象中獲取Data屬性的值(該值就是操作的計算結果)。如果操作以異步方式執行,那么操作可能還未完成(調用一個(gè)異步操作的begin方法的程序可以在begin方法結束后的任何時(shí)間調用end方法,因此end方法必須確保在其返回之前操作已經(jīng)完成)。在本例中,在A(yíng)syncResult對象指明操作已經(jīng)結束前,該方法將一直處于等待狀態(tài)。只有操作結束后,該方法才從 AsyncResult中提起數據并返回該數據。
7. 添加如下方法方法至AdventureWorksAdmin類(lèi)
如果begin方法決定異步地執行任務(wù),那么begin方法將通過(guò)在后臺創(chuàng )建一個(gè)新線(xiàn)程模擬執行計算,新線(xiàn)程將休眠30秒。當后臺線(xiàn)程開(kāi)始休眠時(shí),注冊EndAysncSleep方法為一個(gè)回調方法。30秒之后,操作系統激活后臺線(xiàn)程并調用該方法。該方法填充AysncResult對象的Data屬性, 然后指明操作現在已經(jīng)完成。這將釋放服務(wù)的主線(xiàn)程,因為主線(xiàn)程在等待end方法,end方法結束后允許服務(wù)的主線(xiàn)程向客戶(hù)端返回數據。
請注意,操作的返回值是不同的,這取決于服務(wù)執行同步操作(返回55555555)還是異步操作(返回999999)。
8. 生成解決方案。
練習:在WCF客戶(hù)端程序中調用CalculateTotalValueOfStock操作
1. 在A(yíng)dventureWokrsAdminTestClient項目中,展開(kāi)服務(wù)引用文件夾,然后在A(yíng)dventureWorksAdmin服務(wù)引用上點(diǎn)擊右鍵,然后點(diǎn)擊更新服務(wù)引用。
該操作將生產(chǎn)一個(gè)新版本的客戶(hù)端代理,該代理包含CalculateTotalValueOfStock操作
2. 在解決方案瀏覽器窗口,確保顯示所有文件復選框被選中。
3. 展開(kāi)AdventureWorksAdmin服務(wù)引用,展開(kāi)Reference.svcmap文件夾,然后打開(kāi)Reference.cs文件。檢查 AdministrativeService接口中的服務(wù)合約的定義。請注意名為CalculateTotalValueOfStock新操作,你會(huì )發(fā)現 該實(shí)現該操作的方法并沒(méi)有標記為begin或end。實(shí)際上,該操作以異步地方式實(shí)現,并對客戶(hù)端程序完全是透明的。
4. 編輯Program.cs文件,刪除try代碼片段中調用GenerateDailySalesReport操作和Console.WriteLine語(yǔ)句。然后添加下面代碼
上述代碼三次調用CalculateTotalValueOfStock方法并顯示調用的結果。如果順利,服務(wù)這些調用中至少有一個(gè)與其他兩個(gè)以不同的方式執行(要么同步要么異步)。
5. 在非調適模式下運行解決方案
接下來(lái)將要發(fā)生的結果取決于服務(wù)生成的隨機數,該隨機數決定操作是同步地執行還是異步地執行。如果你不幸運,你必須等待20秒才會(huì )看到第一個(gè)消息提示框:
這是因為CalculateTotalValueOfStock方法中生成的隨機數產(chǎn)生了一個(gè)偶數,然后將同步地執行操作。該消息提示框之后立即出現下面的消息提示框:
當你點(diǎn)擊確認按鈕后,你將看到結果55555555顯示在客戶(hù)端程序的控制臺窗口中。
如果你只看到第二個(gè)消息提示框,那么BeginCalculateTotalValueOfStock已經(jīng)決定執行異步地方法。當你關(guān)閉消息提示框后,你需要等待30秒,你將會(huì )看到下面的消息提示框出現:
999999也將顯示在客戶(hù)端程序的控制臺窗口中??蛻?hù)端程序調用CalculateTotalValueOfStock操作的每個(gè)方法都將重復上述過(guò)程。
6. 在客戶(hù)端程序的控制臺窗口中按ENTER鍵停止客戶(hù)端。
到目前為止,一切都相當好。你已經(jīng)解決了許多麻煩, 這樣可以允許服務(wù)決定最好的策略執行長(cháng)時(shí)間或者耗費資源的造作。但糟糕的是,到目前為止,客戶(hù)端都的一切仍舊使用同步方式;每次調用 CalculateTotalValueOfStock操作都將被阻塞直到該操作完成。幸運的是,你可以通過(guò)在使用svcutil工具是添加/async 參數生成支持客戶(hù)端異步操作的代理。這就是下面練習需要完成的。
練習:在WCF客戶(hù)端程序中異步調用CalculateTotalValueOfStock操作
1. 在A(yíng)dventureWokrsAdminTestClient項目中,展開(kāi)服務(wù)引用文件夾,在A(yíng)dventureWorksAdmin服務(wù)引用上點(diǎn)擊右鍵,然后點(diǎn)擊配置服務(wù)引用。在服務(wù)引用設置對話(huà)框中,選擇生成異步操作復選框,然后點(diǎn)擊確認按鈕
2. 檢查Reference.svcmap文件夾下的Reference.cs文件。
你將看到客戶(hù)端代理現在對服務(wù)合約的兩個(gè)操作都包含了begin和end方法,因為你可以異步地調用它們(每個(gè)操作的同步版本的方法也包含在代理中)。這些改變僅由客戶(hù)端代理實(shí)現;服務(wù)實(shí)際上并不關(guān)注它們。
3. 編輯AdventureWokrsAdminTestClient項目下的Program.cs文件。移除調用CalculateTotalValueOfStock,Console.WriteLine以及關(guān)閉代理等語(yǔ)句。然后添加下列代碼:
上述代碼三次調用客戶(hù)端的異步版本的CalculateTotalValueOfStock方法。結果將在一個(gè)名為 CalculateTotalValueOfStockCallback的方法中處理,該方法將在下一步中添加。Proxy對象作為狀態(tài)參數傳入 CalculateTotalValueOfStockCallback方法。
移除proxy.Close方法非常重要。如果你此時(shí)關(guān)閉代理,WCF運行時(shí)將在異步調用完成之前銷(xiāo)毀客戶(hù)端的通道堆棧,那么客戶(hù)端程序將不能獲取服務(wù)的相應。
4. 添加客戶(hù)端的回調方法
該方法是一個(gè)回調方法。當CalculateTotalValueOfStock操作完成,代理將執行該方法。它從服務(wù)的回傳中獲取對象(該對象為狀態(tài)對 象,其為proxy的一個(gè)引用,該引用在客戶(hù)端程序調用BeginCalculateTotalValueOfStock方法時(shí)作為第三個(gè)參數傳入 BeginCalculateTotalValueOfStock方法中),該回調方法還使用該對象調用 EndCalculateTotalValueOfStock方法。End方法的返回值就是來(lái)自服務(wù)計算的存貨總價(jià)值。
5. 在非調適模式下運行解決方案。
客戶(hù)端程序開(kāi)始然后立即顯示"Press ENTER to finish"。這是因為調用BeginCalculateTotalValueOfStock方法將不再阻塞客戶(hù)端程序。
現在不要按ENTER鍵;允許客戶(hù)端程序繼續執行。在20秒或者30秒之后,你將看到上一個(gè)練習中的消息提示框,該消息提示框指明服務(wù)同步地還是異步地執行每個(gè)請求。當所有操作完成后,計算結果將出現在客戶(hù)端程序控制臺窗口中。
6. 當所有結果都顯示后,按ENTER鍵關(guān)閉客戶(hù)端程序控制臺窗口。
從這些練習中,你現在應該已經(jīng)理解了在客戶(hù)端異步調用操作和在服務(wù)總實(shí)現支持異步操作的不同。開(kāi)發(fā)人員可以決定是否使用一對方法實(shí)現一個(gè)操作以實(shí)現IAsyncResult設計模式以獨立于任何客戶(hù)端程序。這些方法以單個(gè)方法出現在客戶(hù)端程序,并且其實(shí)現是完全透明的。
相似地,當創(chuàng )建一個(gè)WCF客戶(hù)端程序,開(kāi)發(fā)人員希望調用異步地操作時(shí),僅僅需要生產(chǎn)一個(gè)異步代理(要么使用添加服務(wù)引用向導,要么使用svcutil工具是添加/async參數)??蛻?hù)端程序是否同步地調用操作對于服務(wù)是透明的。
最后,你應該認識到盡管客戶(hù)端程序可以異地調用服務(wù)的操作,服務(wù)仍然可以選擇使用同步方式實(shí)現操作;反之亦反??蛻?hù)端和服務(wù)其相應的實(shí)現部分是相當靈活的。
更進(jìn)一步,你還可以按照下列方式對服務(wù)的操作既定義同步版本又定義異步版本。
但是,如果你這么做,兩個(gè)操作都將出現在服務(wù)的WSDL描述的同一個(gè)動(dòng)作中。在這種情況下,WCF將不會(huì )拋出異常,并且比起異步版本的操作WCF將始終使用 同步版本的操作(因為WCF假設同步操作版本可以更快的獲得輸出)。因此,不要在同一個(gè)服務(wù)中對同一個(gè)操作既定義同步版本又定義異步版本。