在撰寫使用 Web 服務(wù)的應(yīng)用程序時(shí),無(wú)可避免地,您必須處理透過(guò)網(wǎng)絡(luò)呼叫所涉及的潛在因素。在某些情況下,尤其是在擁有很大頻寬的私人網(wǎng)絡(luò)上,可能在半秒內(nèi)就完成呼叫,等待時(shí)間很短。不過(guò),如果您要透過(guò) Internet 傳送要求至遠(yuǎn)程位置,或要發(fā)出的呼叫需要很多處理時(shí)間,則您需要開(kāi)始考慮長(zhǎng)時(shí)間延遲對(duì)您應(yīng)用程序的影響多大。例如,Microsoft® Windows® Forms 應(yīng)用程序在等待 Web 服務(wù)的呼叫傳回時(shí),看起來(lái)是凍結(jié)的。如果您是從 Microsoft® ASP.NET 網(wǎng)頁(yè)呼叫 Web 服務(wù),您會(huì)發(fā)現(xiàn),許多個(gè) Web 服務(wù)呼叫會(huì)讓網(wǎng)頁(yè)顯示速度慢好幾倍。如果您有一個(gè)應(yīng)用程序發(fā)出許多 Web 服務(wù)呼叫,則如何盡量有效率地呼叫它們,值得您思考。
許多這些問(wèn)題的解決之道就是使 Web 服務(wù)呼叫異步化。異步呼叫會(huì)立即傳回,然后使用另一種機(jī)制來(lái)表示該呼叫何時(shí)實(shí)際完成。這可讓您的應(yīng)用程序執(zhí)行其它作業(yè),例如任何必要的背景處理、響應(yīng)使用者與 UI 的互動(dòng)、提供對(duì)于要求現(xiàn)行狀態(tài)的響應(yīng)、甚至起始其它 Web 服務(wù)呼叫。我們要看一看 Microsoft® .NET Framework 如何提供支持以透過(guò) HTTP 發(fā)出異步 Web 服務(wù)呼叫,以及我們?cè)谠S多一般實(shí)務(wù)中如何應(yīng)用它們。
建置區(qū)塊
在 Microsoft Visual Studio® .NET 選擇 [加入 Web 參考] 選項(xiàng)時(shí),會(huì)為您建立一個(gè)類別,它繼承自 System.Web.Services.Protocols.SoapHttpClientProtocol。SoapHttpClientProtocol 有一個(gè)受保護(hù)的函式叫作 Invoke,當(dāng)您對(duì) Web 服務(wù)所公開(kāi)的其中一個(gè)方法發(fā)出呼叫時(shí),實(shí)際上會(huì)使用此函式。對(duì)于 Web 服務(wù)的 WSDL 中所定義的每一個(gè) Web 方法,精靈會(huì)以適當(dāng)?shù)拿Q參數(shù)和回復(fù)值建立一個(gè)函式。每一個(gè)函式會(huì)呼叫 SoapHttpClientProtocol 類別的 Invoke 函式傳入?yún)?shù)信息,諸如此類。在本文件中,我建立一個(gè) Web 服務(wù),它使用的方法故意花費(fèi)很長(zhǎng)時(shí)間才傳回。這個(gè) Web 方法叫作 DelayedResponse,它以整數(shù)作為它的唯一參數(shù)并傳回一個(gè)字符串。[加入 Web 參考] 選項(xiàng)會(huì)為此方法產(chǎn)生下列 Proxy 程序代碼: Public Function DelayedResponse(ByVal waitPeriod As Integer) _ As String Dim results() As Object _ = Me.Invoke("DelayedResponse", _ New Object() {waitPeriod}) Return CType(results(0),String) End Function
Invoke 方法使用兩個(gè)參數(shù):一個(gè)函式名稱和一個(gè)保留要傳遞至函式的參數(shù)之對(duì)象數(shù)組。Invoke 方法傳回對(duì)象數(shù)組,在本范例中,它只包含一個(gè)元素—從我們的函式傳回的字符串。這是發(fā)出同步呼叫的機(jī)制,這些呼叫要等到接收響應(yīng)之后才傳回。
SoapHttpClientProtocol 類別也有一個(gè)叫作 BeginInvoke 的方法,它是用來(lái)激活異步要求的機(jī)制。[加入 Web 參考] 所建立的類別也會(huì)建立一個(gè)叫作 BeginDelayedResponse 的公用函式來(lái)配合我們之前看過(guò)的區(qū)塊式 DelayedResponse 函式。BeginDelayedResponse 的程序代碼顯示如下。 Public Function BeginDelayedResponse( _ ByVal waitPeriod As Integer, _ ByVal callback As System.AsyncCallback, _ ByVal asyncState As Object) As System.IAsyncResult Return Me.BeginInvoke("DelayedResponse", _ New Object() {waitPeriod}, _ callback, _ asyncState) End Function
BeginDelayedResponse 使用 BeginInvoke 方法,它在許多方面類似我們之前使用過(guò)的 Invoke 方法。前兩個(gè)參數(shù)與 Invoke 方法使用的參數(shù)相同。不過(guò),BeginInvoke 還有兩個(gè)額外參數(shù),而且它不再傳回對(duì)象數(shù)組。不過(guò),其主要差異是 BeginInvoke 會(huì)立刻傳回,而不等待 Web 服務(wù)呼叫完成。
BeginInvoke 的兩個(gè)額外參數(shù)的第一個(gè)叫作 System.AsyncCallback。這就是所謂的委派,基本上它是一個(gè)用來(lái)宣告 Managed 程序代碼中的函式指針的機(jī)制。在本范例中,一旦 Web 方法呼叫完成且我們收到響應(yīng),則會(huì)呼叫此函式。
BeginInvoke 的最后一個(gè)參數(shù)就叫作 asyncState,它被宣告為對(duì)象類型。這可以是您要用來(lái)追蹤此要求的任何項(xiàng)目。您可以對(duì)許多不同的異步要求使用相同的回呼函式,以便區(qū)別某個(gè)呼叫的響應(yīng)與另一個(gè)呼叫的響應(yīng),您可以把關(guān)于該呼叫的信息放在 asyncState 參數(shù)中,它將供您的回呼函式使用。
關(guān)于 BeginInvoke 還有一點(diǎn)不同,即它傳回的內(nèi)容。很顯然,如果呼叫未完成,則它不能傳回響應(yīng)資料至 Web 方法。它傳回的是 System.IAsyncResult 接口指針。您可以使用 IAsyncResult 接口指針取得關(guān)于該要求的信息。IAsyncResult 顯示四個(gè)公用屬性,如下所示:
屬性 |
說(shuō)明 |
AsyncState |
這是傳入 BeginInvoke 方法的第四個(gè)參數(shù)的資料。 |
AsyncWaitHandle |
這是 WaitHandle 對(duì)象,可用來(lái)封鎖執(zhí)行緒目前的執(zhí)行,直到有一或多個(gè) Web 服務(wù)呼叫完成為止。 |
CompletedSynchronously |
此參數(shù)不適用于 Web 服務(wù)呼叫。IAsyncResult 接口是用于一些 I/O 作業(yè),此屬性可讓您知道異步 I/O 作業(yè)要求是否太快完成,它甚至在 Begin 函式傳回之前完成。 |
IsCompleted |
這是一個(gè)旗標(biāo),您可用它來(lái)判斷呼叫是否完成。 |
IAsyncResult 指針是最后可讓您和系統(tǒng)分辨不同的異步完成的指針。它也提供您不同選項(xiàng)來(lái)判斷呼叫何時(shí)完成。我們看看如何使用這些選項(xiàng)。
一如前述,[加入 Web 參考] 選項(xiàng)會(huì)為使用 BeginInvoke 的服務(wù)所提供的每一個(gè) Web 方法建立一個(gè)函式。在我們的范例中,所產(chǎn)生的函式叫作 BeginDelayedResponse,它是 BeginInvoke 方法的一個(gè)極輕巧的包裝函式,它顯示 Web 呼叫方法的參數(shù)以及 BeginInvoke 提供的 callback 和 asyncState 參數(shù)。接下來(lái),我們要看看用來(lái)提出異步要求和判斷何時(shí)完成要求的三個(gè)選項(xiàng)。
發(fā)出異步呼叫的三個(gè)選項(xiàng)
每一個(gè)應(yīng)用程序都不一樣,因此,發(fā)出異步呼叫的案例對(duì)某些應(yīng)用程序可能有效,但不一定對(duì)其他應(yīng)用程序有效。在提供發(fā)出異步呼叫的方式上,.NET Framework 有很大的彈性。您可以輪詢看看何時(shí)完成要求、封鎖 WaitHandle 或等待回呼函式,F(xiàn)在我們個(gè)別來(lái)看一看這些方式。
輪詢完成狀態(tài)
從 BeginDelayedResponse 函式傳回的 IAsyncResult 接口有一個(gè) IsCompleted 屬性,可檢查此屬性來(lái)判斷是否完成要求。您可以輪詢此屬性,直到它傳回 True 值為止。示范此方式的簡(jiǎn)短程序代碼如下所示: ' 輪詢會(huì)束縛處理器的程序代碼 Dim proxy as New localhost.Service1() Dim result as IAsyncResult Result = proxy.BeginDelayedResponse(2000, _ Nothing, _ Nothing) While (result.IsCompleted = False) ' 執(zhí)行一些處理 ... Wend Dim response as String response = proxy.EndDelayedResponse(result)
輪詢完成狀態(tài)是一個(gè)很直接的方式,但它的確有一些缺點(diǎn)。此程序代碼顯示如何對(duì) BeginDelayedResponse 發(fā)出起始呼叫,它傳遞數(shù)字 2000 作為要傳至 Web 方法的參數(shù),然后將回呼和 asyncState 設(shè)定為 Nothing。然后我們有一個(gè) while 循環(huán)來(lái)輪詢 IsCompleted 屬性,直到它為 True 為止。當(dāng)呼叫完成且 IsCompleted 屬性設(shè)定為 True 時(shí),我們會(huì)脫離 while 循環(huán),然后我們使用 [加入 Web 參考] 產(chǎn)生的類別中的另一個(gè)函式叫作 EndDelayedResponse,來(lái)取得響應(yīng)。EndDelayedResponse 是 SoapHttpClientProtocol 類別的 EndInvoke 方法的包裝函式,而且是從 Web 方法呼叫取得傳回的資料的機(jī)制。當(dāng)您知道 Web 服務(wù)呼叫完成時(shí)即可使用;它傳回的信息與 Invoke 方法傳回給區(qū)塊式呼叫的信息一樣。在所有三個(gè)異步案例中,我們會(huì)使用 EndDelayedResponse 方法取得 Web 服務(wù)呼叫的結(jié)果。請(qǐng)注意,如果在完成要求之前呼叫 EndDelayedResponse,則它直接封鎖,直到要求真正完成為止。
在使用輪詢判斷呼叫是否完成時(shí),您需要注意的其中一個(gè)問(wèn)題是,您一不小心就會(huì)消耗掉很多的機(jī)器 CPU 周期。例如,如果 while 循環(huán)內(nèi)沒(méi)有程序代碼,則執(zhí)行此程序代碼的執(zhí)行緒可占據(jù)您機(jī)器上的大部份資源。事實(shí)上,它會(huì)吃掉很多處理時(shí)間,使得要傳送 Web 服務(wù)要求和接收響應(yīng)的基礎(chǔ)程序代碼因而延遲。因此,使用輪詢時(shí)要小心。
如果您真的想要等到 Web 服務(wù)要求完成,您也許可以使用我們接下來(lái)要看的 WaitHandle 方式。不過(guò),如果您有很多處理要完成,而且您只想要偶而檢查一下 Web 服務(wù)呼叫是否結(jié)束,那么使用輪詢并不是一個(gè)壞的方案。許多時(shí)候,應(yīng)用程序會(huì)使用輪詢與其中另一個(gè)異步方式的組合。例如,您可以同步發(fā)出 Web 服務(wù)呼叫,因?yàn)槟幸恍┍尘疤幚硪獔?zhí)行,可是一旦背景處理執(zhí)行完畢,您想要封鎖直到 Web 服務(wù)完成為止。在此情況下,您可以在執(zhí)行處理時(shí)偶而輪詢一下,但是一旦背景處理完成,就使用 WaitHandle 來(lái)等候結(jié)果。
使用 WaitHandle
當(dāng)您需要發(fā)出異步呼叫,但您不想釋放目前在執(zhí)行的執(zhí)行緒,則 WaitHandle 很好用。例如,如果您從 ASP.NET 應(yīng)用程序內(nèi)發(fā)出異步 Web 服務(wù)呼叫,然后從處理 ASP.NET 應(yīng)用程序的事件中返回,則您可能沒(méi)有機(jī)會(huì)在傳回給使用者的資料中并入 Web 服務(wù)呼叫中的資料。運(yùn)用 WaitHandle,您就可以在發(fā)出 Web 服務(wù)呼叫之后做一些處理,然后封鎖直到 Web 服務(wù)呼叫完成為止。如果您要從 ASP.NET 網(wǎng)頁(yè)內(nèi)發(fā)出多個(gè) Web 服務(wù)呼叫,則 WaitHandle 特別有用。
WaitHandle 對(duì)象的存取權(quán)是由 BeginDelayedResponse 函式傳回的 IAsyncResult 提供。下列程序代碼顯示使用 WaitHandle 方式的簡(jiǎn)單案例。 ' 簡(jiǎn)單的 WaitHandle 程序代碼 Dim proxy As New localhost.Service1() Dim result As IAsyncResult result = proxy.BeginDelayedResponse(2000, Nothing, Nothing) ' 做一些處理。 ' ... ' 處理完成。等候完成。 result.AsyncWaitHandle.WaitOne() Dim response As String response = proxy.EndDelayedResponse(result)
在上述程序代碼中,我們使用了 WaitHandle 對(duì)象的 WaitOne 方法來(lái)等候這個(gè)控制代碼。WaitHandle 類別中也有靜態(tài)方法,叫作 WaitAll 和 WaitAny。這兩個(gè)靜態(tài)方法以 WaitHandle 數(shù)組作為參數(shù),它們可能在所有呼叫完成后才傳回,或一有呼叫完成即傳回,視您呼叫的函式而定。假設(shè)您叫作三個(gè)不同的 Web 服務(wù)。您可以異步地呼叫每一個(gè)服務(wù),把 WaitHandle 放在每一個(gè)的數(shù)組中,然后呼叫 WaitAll 方法直到它們完成為止。這可讓 Web 服務(wù)呼叫同時(shí)執(zhí)行。如果您同步執(zhí)行此動(dòng)作,則無(wú)法并行地執(zhí)行它們,導(dǎo)致執(zhí)行時(shí)間變成三倍左右。
請(qǐng)注意,WaitOne、WaitAll 和 WaitAny 方法都有以逾時(shí) (Timeout) 作為參數(shù)的選項(xiàng)。這可讓您控制您要等候 Web 服務(wù)完成的時(shí)間。如果方法逾時(shí),它們會(huì)傳回 False 值。這可讓您在重新等候之前執(zhí)行更多處理,或讓您有機(jī)會(huì)取消要求。
使用回呼
執(zhí)行異步 Web 服務(wù)呼叫的第三個(gè)方式是使用回呼。如果您要發(fā)出很多同步 Web 服務(wù)呼叫,則使用回呼很有效率。它在 Web 服務(wù)呼叫的結(jié)果上執(zhí)行背景處理很有幫助。不過(guò),它是比其它兩個(gè)方法更復(fù)雜的方式。在 Microsoft Windows® 應(yīng)用程序中使用回呼特別適合,因?yàn)檫@可避免封鎖 Window 的訊息執(zhí)行緒。
回呼會(huì)在 Web 服務(wù)呼叫完成時(shí)導(dǎo)致回呼函式被呼叫。不過(guò),在發(fā)出原始 BeginInvoke 呼叫的執(zhí)行緒內(nèi)容中,不可呼叫回呼函式。這會(huì)造成傳送指令至 Windows Form 應(yīng)用程序的控件時(shí)產(chǎn)生問(wèn)題,因?yàn)槟切┲噶畋仨毷菑奶囟▓?zhí)行緒呼叫,該執(zhí)行緒為該特定 Window 處理訊息處理。還有,有一個(gè)方法可解決此問(wèn)題。
下列程序代碼顯示一個(gè)簡(jiǎn)單案例,它使用回呼且 Web 服務(wù)呼叫的結(jié)果會(huì)顯示在卷標(biāo)控件中。 Dim proxy as localhost.Service1 Private Delegate Sub MyDelegate(ByVal response As String) Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click proxy = New localhost.Service1() proxy.BeginDelayedResponse(2000, _ New AsyncCallback(AddressOf Me.ServiceCallback), _ Nothing) End Sub Private Sub ServiceCallback(ByVal result As IAsyncResult) Dim response As String response = proxy.EndDelayedResponse(result) Label1.Invoke( _ New MyDelegate(AddressOf Me.DisplayResponse), _ New Object() {response}) End Sub Private Sub DisplayResponse(ByVal response As String) Label1.Text = response End Sub
在此程序代碼中,我們呼叫 Button1_Click 事件中的 BeginDelayedResponse,然后從子程序傳回。請(qǐng)注意,本案例不傳遞 Nothing 作為第二個(gè)參數(shù),而是傳遞 AsyncCallback 對(duì)象。基本上,這只是包裝回呼函式地址的一個(gè)方式,叫作 ServiceCallback。ServiceCallback 函式必須是一個(gè)子程序,它采取 IAsyncResult 類型的單一參數(shù)。SoapHttpClientProtocol 類別將呼叫此函式,并將提供對(duì)應(yīng)完成的 Web 服務(wù)呼叫的 IAsyncResult 接口指針。同樣地,我們使用 EndDelayedResponse 方法取得結(jié)果,但現(xiàn)在我們有一個(gè)小問(wèn)題。
因?yàn)榛睾艨赡懿辉?/FONT> Window 的主要執(zhí)行緒中,所以我們需要使用不同的 Invoke 方法來(lái)設(shè)定 Window 中的卷標(biāo)文字。所有控件都有 Invoke 方法供您呼叫,以便呼叫一個(gè)在其主要訊息執(zhí)行緒中執(zhí)行的函式。在卷標(biāo)控件上使用 Invoke 類似我們已經(jīng)看過(guò)的其它一些內(nèi)容。我們必須提供要它呼叫的函式地址給 Invoke,而且我們要在對(duì)象數(shù)組中包括要傳給該函式的任何參數(shù),作為第二個(gè)參數(shù)。在此范例中,我們必須宣告一個(gè)叫作 MyDelegate 的委派類型,來(lái)通知 Invoke 方法我們要它呼叫的函式語(yǔ)法。在此范例中,此函式叫作 DisplayResponse,且只有一個(gè)參數(shù)—從 Web 服務(wù)呼叫傳回的字符串。將在卷標(biāo)控件的適當(dāng)執(zhí)行緒內(nèi)呼叫 DisplayResponse,而且它在設(shè)定應(yīng)用程控項(xiàng)文字方面沒(méi)有問(wèn)題。
進(jìn)階議題
到目前為止,我們看過(guò)的范例都很直接。它們做了許多的假設(shè),例如我們的 Web 服務(wù)呼叫成功,并簡(jiǎn)化為一次僅發(fā)出單一呼叫的事實(shí),F(xiàn)在我們要看看有多個(gè) Web 服務(wù)呼叫的案例。我們要看看如何處理這些呼叫可能產(chǎn)生的錯(cuò)誤,并看看必要時(shí)如何取消呼叫。
使用 asyncState
我們先看一個(gè)范例,其中我們除了 Nothing 之外還傳遞其它項(xiàng)目作為 BeginDelayedResponse 呼叫的最后一個(gè)參數(shù)。如果您還記得,這是 asyncState 參數(shù),它直接宣告為一個(gè)對(duì)象,F(xiàn)在我們要使用此參數(shù)來(lái)發(fā)出多個(gè) Web 服務(wù)呼叫,使響應(yīng)與適當(dāng)?shù)囊螽a(chǎn)生關(guān)聯(lián)。
我現(xiàn)在要看的案例是對(duì) DelayedResponse Web 方法發(fā)出三個(gè)不同的呼叫。然后它會(huì)在我的 Windows 應(yīng)用程序的三個(gè)不同卷標(biāo)控件中顯示這些呼叫的結(jié)果。第一個(gè)呼叫的結(jié)果應(yīng)該顯示在第一個(gè)卷標(biāo),第二個(gè)呼叫的結(jié)果應(yīng)該顯示在第二個(gè)卷標(biāo),第三個(gè)呼叫的結(jié)果應(yīng)該顯示在第三個(gè)卷標(biāo)。我要對(duì)每一個(gè) Web 服務(wù)呼叫使用相同的回呼函式,而且為了判斷哪一個(gè)卷標(biāo)配哪一個(gè)呼叫,我會(huì)以 asyncState 參數(shù)傳遞卷標(biāo)對(duì)象。為了制造更多的混淆,我讓每一個(gè)呼叫要完成所花的時(shí)間隨機(jī)化。其做法的程序代碼顯示如下。 Dim proxy As localhost.Service1 Private Delegate Sub LabelDelegate( _ ByVal responseLabel As Label, _ ByVal response As String) Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button7.Click proxy = New localhost.Service1() ClearForm() Dim r As New Random() proxy.BeginDelayedResponse(r.Next(1, 10000), _ New AsyncCallback(AddressOf Me.ServiceCallback), _ Label1) proxy.BeginDelayedResponse(r.Next(1, 10000), _ New AsyncCallback(AddressOf Me.ServiceCallback), _ Label2) proxy.BeginDelayedResponse(r.Next(1, 10000), _ New AsyncCallback(AddressOf Me.ServiceCallback), _ Label3) End Sub Private Sub ServiceCallback(ByVal result As IAsyncResult) Dim response As String response = proxy.EndDelayedResponse(result) Dim responseLabel As Label = result.AsyncState responseLabel.Invoke( _ New LabelDelegate(AddressOf Me.DisplayResponses), _ New Object() {responseLabel, response}) End Sub Private Sub DisplayResponses(ByVal responseLabel As Label, _ ByVal response As String) responseLabel.Text = response Form1.ActiveForm.Refresh() End Sub
透過(guò)傳至回呼的 IAsyncResult 參數(shù),傳入 asyncState 參數(shù)的卷標(biāo)對(duì)象可在我們的回呼函式中使用。我們也修改了前一個(gè)范例中的委派,來(lái)采用另一個(gè)參數(shù)。另一個(gè)參數(shù)同樣是卷標(biāo),因此委派知道哪一個(gè)控件要設(shè)定文字。
也許您常常要傳遞更多信息,而不止是透過(guò) asyncState 的單一對(duì)象。因?yàn)?/FONT> asyncState 僅宣告為一個(gè)對(duì)象,因此也可以用它來(lái)傳遞復(fù)合信息,例如對(duì)象數(shù)組或您要使用的一些復(fù)合結(jié)構(gòu)。在我們的案例中,我們只使用一個(gè) Proxy 對(duì)象,但是在某些案例中,您可能有不同的 Proxy 對(duì)象給每一個(gè) Web 服務(wù)呼叫。如果是這種情況,您也可以在 asyncState 資料中并入該 Proxy 對(duì)象。Web 服務(wù)呼叫的任何其它特定資料也可以考慮并入其中。
偵測(cè)錯(cuò)誤
如果您習(xí)慣于發(fā)出同步 Web 服務(wù)呼叫,那么您可能也習(xí)慣透過(guò)以包裝遠(yuǎn)程呼叫的 try...catch 區(qū)塊來(lái)偵測(cè)錯(cuò)誤。對(duì)異步呼叫做同樣的事似乎令人感到困惑。我必須在 try...catch 區(qū)塊中包裝 BeginDelayedResponse 或 EndDelayedResponse?也許我必須兩個(gè)都包裝!
其實(shí),對(duì)于正常 SOAP 錯(cuò)誤以及其它與傳送相關(guān)的錯(cuò)誤,我們只需要在 try...catch 區(qū)塊中包裝 EndDelayedResponse 呼叫。BeginDelayedResponse 呼叫不會(huì)產(chǎn)生錯(cuò)誤,因?yàn)樗鼤?huì)立刻傳回,而不會(huì)等候是否有任何問(wèn)題。當(dāng)您在等待回呼被呼叫,或等待 Wait 呼叫解除封鎖時(shí),并不會(huì)隨機(jī)發(fā)生錯(cuò)誤。所發(fā)生的錯(cuò)誤會(huì)透過(guò)您使用的機(jī)制來(lái)觸發(fā)完成,機(jī)制可以是將 IsCompleted 屬性設(shè)定為 True、或是觸發(fā) WaitHandle 或是呼叫回呼。唯有當(dāng)您呼叫 EndDelayedResponse 時(shí)才會(huì)觸發(fā)錯(cuò)誤,它提供您關(guān)于失敗的信息。
為了證明我的應(yīng)用程序有可能失敗,我在回呼函式中的 EndDelayedResponse 呼叫周圍增加了一個(gè) try...catch 區(qū)塊。我順便修改了失敗的響應(yīng)文字。 Private Sub ServiceCallback(ByVal result As IAsyncResult) Dim response As String Try response = proxy.EndDelayedResponse(result) Catch e As Exception Response = "失敗" End Try Dim responseLabel As Label = result.AsyncState responseLabel.Invoke( _ New LabelDelegate(AddressOf Me.DisplayResponses), _ New Object() {responseLabel, response}) End Sub
中止要求
在應(yīng)用程序中使用異步要求的其中一個(gè)好處是,當(dāng)呼叫完成時(shí)您不必鎖定使用者接口。當(dāng)然,如果您要讓應(yīng)用程序使用者繼續(xù)與應(yīng)用程序互動(dòng),那么您可以并入的一個(gè)極常見(jiàn)的功能,就是取消未完成要求的一個(gè)按鈕。如果基于某種原因,Web 服務(wù)呼叫需要很長(zhǎng)的時(shí)間才會(huì)完成,那么應(yīng)該讓使用者能夠判斷他們要等待多久。
中止要求很簡(jiǎn)單。使用 Proxy 類別上可用的 Abort 方法,那是在您使用 [加入 Web 參考] 選項(xiàng)時(shí)建立的。關(guān)于中止要求,請(qǐng)記住幾件事。當(dāng)您呼叫 Abort 方法時(shí),任何未完成的要求仍然會(huì)完成,但它們完成時(shí)會(huì)有錯(cuò)誤。這表示,如果您使用回呼,則仍然會(huì)對(duì)每一個(gè)未完成的要求呼叫您的回呼函式。呼叫 EndInvoke 方法 (或是我們案例中的包裝函式 EndDelayedResponse) 時(shí),會(huì)產(chǎn)生錯(cuò)誤,指出基礎(chǔ)連接已關(guān)閉。
繁衍執(zhí)行緒以發(fā)出同步呼叫
還有另一個(gè)選項(xiàng)可用來(lái)解決異步呼叫的許多問(wèn)題。那就是直接繁衍執(zhí)行緒,并讓該執(zhí)行緒發(fā)出容易撰寫程序代碼的同步呼叫。在某些案例中這是合理的,但您應(yīng)該了解關(guān)于此方式的一些議題。
首先,這會(huì)使 Web 服務(wù)呼叫程序代碼更簡(jiǎn)單,但也可能會(huì)需要實(shí)作與我們使用過(guò)的部份程序代碼一樣復(fù)雜的程序代碼,才能管理和在執(zhí)行緒之間通訊。如果您要發(fā)出很多個(gè) Web 服務(wù)呼叫,則您系統(tǒng)可能因?yàn)橛刑鄠(gè)執(zhí)行緒要管理而負(fù)擔(dān)過(guò)重。以正?蛻舳藨(yīng)用程序而言,這可能不是什么大不了的事,但如果您要從 ASP.NET 網(wǎng)頁(yè)發(fā)出呼叫,則您必須考慮有多少個(gè)使用者會(huì)同時(shí)使用您的系統(tǒng)。新增兩個(gè)執(zhí)行緒從 ASP.NET 網(wǎng)頁(yè)執(zhí)行 Web 服務(wù)呼叫,看起來(lái)負(fù)擔(dān)不大,但如果有 200 個(gè)人同時(shí)使用同一張網(wǎng)頁(yè)時(shí)會(huì)怎么樣?現(xiàn)在您談的是 400 個(gè)新執(zhí)行緒,那負(fù)擔(dān)一定很大。雖然執(zhí)行異步呼叫的回呼機(jī)制使用額外執(zhí)行緒來(lái)進(jìn)行回呼,但它們重復(fù)使用執(zhí)行緒集區(qū),以提高回呼效率—同時(shí)避免有數(shù)百個(gè)執(zhí)行緒同時(shí)執(zhí)行的問(wèn)題。
如果繁衍背景執(zhí)行緒,使 Web 服務(wù)呼叫仍然在合理范圍內(nèi),那么您應(yīng)該考慮使用委派和處理執(zhí)行緒集區(qū)來(lái)繁衍這些執(zhí)行緒。然后您可以使用異步范例 (類似我們用于 Web 服務(wù)回呼的范例) 并發(fā)出函式呼叫來(lái)與執(zhí)行緒上的控件互動(dòng),藉此呼叫這些方法。有關(guān)使用委派發(fā)出一般異步方法呼叫的其它詳細(xì)信息,請(qǐng)參閱 Richard Grimes 的文章《.Net Delegates: Making Asynchronous Method Calls in the .Net Environment》(英文)。
結(jié)論異步發(fā)出 Web 服務(wù)呼叫,可能是透過(guò) HTTP 從 .NET Framework 應(yīng)用程序使用 Web 服務(wù)的好點(diǎn)子。大部份真正的應(yīng)用程序會(huì)想要使用此功能來(lái)有效呼叫 Web 服務(wù),而不讓可能需要花費(fèi)長(zhǎng)時(shí)間的網(wǎng)絡(luò)呼叫,造成應(yīng)用程序被封鎖。.NET Framework 在支持透過(guò) HTTP 的異步 Web 服務(wù)呼叫的方式上很有彈性,它提供開(kāi)發(fā)人員很多控制權(quán),來(lái)決定他們要如何處理呼叫的完成。
|