新疆软件开发

本站首页 软件开发 成功案例 公司新闻 公司简介 客服中心 软件技术 网站建设
  您现在的位置: 新疆二域软件开发公司 >> .Net技术 >> 文章正文

透过HTTP进行异步Web Service 呼叫

在撰写使用 Web 服务的应用程序时,无可避免地,您必须处理透过网络呼叫所涉及的潜在因素。在某些情况下,尤其是在拥有很大频宽的私人网络上,可能在半秒内就完成呼叫,等待时间很短。不过,如果您要透过 Internet 传送要求至远程位置,或要发出的呼叫需要很多处理时间,则您需要开始考虑长时间延迟对您应用程序的影响多大。例如,Microsoft® Windows® Forms 应用程序在等待 Web 服务的呼叫传回时,看起来是冻结的。如果您是从 Microsoft® ASP.NET 网页呼叫 Web 服务,您会发现,许多个 Web 服务呼叫会让网页显示速度慢好几倍。如果您有一个应用程序发出许多 Web 服务呼叫,则如何尽量有效率地呼叫它们,值得您思考。

许多这些问题的解决之道就是使 Web 服务呼叫异步化。异步呼叫会立即传回,然后使用另一种机制来表示该呼叫何时实际完成。这可让您的应用程序执行其它作业,例如任何必要的背景处理、响应使用者与 UI 的互动、提供对于要求现行状态的响应、甚至起始其它 Web 服务呼叫。我们要看一看 Microsoft® .NET Framework 如何提供支持以透过 HTTP 发出异步 Web 服务呼叫,以及我们在许多一般实务中如何应用它们。

建置区块

Microsoft Visual Studio® .NET 选择 [加入 Web 参考] 选项时,会为您建立一个类别,它继承自 System.Web.Services.Protocols.SoapHttpClientProtocolSoapHttpClientProtocol 有一个受保护的函式叫作 Invoke,当您对 Web 服务所公开的其中一个方法发出呼叫时,实际上会使用此函式。对于 Web 服务的 WSDL 中所定义的每一个 Web 方法,精灵会以适当的名称参数和回复值建立一个函式。每一个函式会呼叫 SoapHttpClientProtocol 类别的 Invoke 函式传入参数信息,诸如此类。在本文件中,我建立一个 Web 服务,它使用的方法故意花费很长时间才传回。这个 Web 方法叫作 DelayedResponse,它以整数作为它的唯一参数并传回一个字符串。[加入 Web 参考] 选项会为此方法产生下列 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 方法使用两个参数:一个函式名称和一个保留要传递至函式的参数之对象数组。Invoke 方法传回对象数组,在本范例中,它只包含一个元素—从我们的函式传回的字符串。这是发出同步呼叫的机制,这些呼叫要等到接收响应之后才传回。

SoapHttpClientProtocol 类别也有一个叫作 BeginInvoke 的方法,它是用来激活异步要求的机制。[加入 Web 参考] 所建立的类别也会建立一个叫作 BeginDelayedResponse 的公用函式来配合我们之前看过的区块式 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 方法,它在许多方面类似我们之前使用过的 Invoke 方法。前两个参数与 Invoke 方法使用的参数相同。不过,BeginInvoke 还有两个额外参数,而且它不再传回对象数组。不过,其主要差异是 BeginInvoke 会立刻传回,而不等待 Web 服务呼叫完成。

BeginInvoke 的两个额外参数的第一个叫作 System.AsyncCallback。这就是所谓的委派,基本上它是一个用来宣告 Managed 程序代码中的函式指针的机制。在本范例中,一旦 Web 方法呼叫完成且我们收到响应,则会呼叫此函式。

BeginInvoke 的最后一个参数就叫作 asyncState,它被宣告为对象类型。这可以是您要用来追踪此要求的任何项目。您可以对许多不同的异步要求使用相同的回呼函式,以便区别某个呼叫的响应与另一个呼叫的响应,您可以把关于该呼叫的信息放在 asyncState 参数中,它将供您的回呼函式使用。

关于 BeginInvoke 还有一点不同,即它传回的内容。很显然,如果呼叫未完成,则它不能传回响应资料至 Web 方法。它传回的是 System.IAsyncResult 接口指针。您可以使用 IAsyncResult 接口指针取得关于该要求的信息。IAsyncResult 显示四个公用属性,如下所示:

属性

说明

AsyncState

这是传入 BeginInvoke 方法的第四个参数的资料。

AsyncWaitHandle

这是 WaitHandle 对象,可用来封锁执行绪目前的执行,直到有一或多个 Web 服务呼叫完成为止。

CompletedSynchronously

此参数不适用于 Web 服务呼叫。IAsyncResult 接口是用于一些 I/O 作业,此属性可让您知道异步 I/O 作业要求是否太快完成,它甚至在 Begin 函式传回之前完成。

IsCompleted

这是一个旗标,您可用它来判断呼叫是否完成。

IAsyncResult 指针是最后可让您和系统分辨不同的异步完成的指针。它也提供您不同选项来判断呼叫何时完成。我们看看如何使用这些选项。

一如前述,[加入 Web 参考] 选项会为使用 BeginInvoke 的服务所提供的每一个 Web 方法建立一个函式。在我们的范例中,所产生的函式叫作 BeginDelayedResponse,它是 BeginInvoke 方法的一个极轻巧的包装函式,它显示 Web 呼叫方法的参数以及 BeginInvoke 提供的 callback asyncState 参数。接下来,我们要看看用来提出异步要求和判断何时完成要求的三个选项。

发出异步呼叫的三个选项

每一个应用程序都不一样,因此,发出异步呼叫的案例对某些应用程序可能有效,但不一定对其他应用程序有效。在提供发出异步呼叫的方式上,.NET Framework 有很大的弹性。您可以轮询看看何时完成要求、封锁 WaitHandle 或等待回呼函式。现在我们个别来看一看这些方式。

轮询完成状态

BeginDelayedResponse 函式传回的 IAsyncResult 接口有一个 IsCompleted 属性,可检查此属性来判断是否完成要求。您可以轮询此属性,直到它传回 True 值为止。示范此方式的简短程序代码如下所示:

' 轮询会束缚处理器的程序代码
Dim proxy as New localhost.Service1()
Dim result as IAsyncResult
Result = proxy.BeginDelayedResponse(2000, _
                                    Nothing, _
                                    Nothing)
While (result.IsCompleted = False)
    ' 执行一些处理
        ...
Wend
Dim response as String
response = proxy.EndDelayedResponse(result)

轮询完成状态是一个很直接的方式,但它的确有一些缺点。此程序代码显示如何对 BeginDelayedResponse 发出起始呼叫,它传递数字 2000 作为要传至 Web 方法的参数,然后将回呼和 asyncState 设定为 Nothing。然后我们有一个 while 循环来轮询 IsCompleted 属性,直到它为 True 为止。当呼叫完成且 IsCompleted 属性设定为 True 时,我们会脱离 while 循环,然后我们使用 [加入 Web 参考] 产生的类别中的另一个函式叫作 EndDelayedResponse,来取得响应。EndDelayedResponse SoapHttpClientProtocol 类别的 EndInvoke 方法的包装函式,而且是从 Web 方法呼叫取得传回的资料的机制。当您知道 Web 服务呼叫完成时即可使用;它传回的信息与 Invoke 方法传回给区块式呼叫的信息一样。在所有三个异步案例中,我们会使用 EndDelayedResponse 方法取得 Web 服务呼叫的结果。请注意,如果在完成要求之前呼叫 EndDelayedResponse,则它直接封锁,直到要求真正完成为止。

在使用轮询判断呼叫是否完成时,您需要注意的其中一个问题是,您一不小心就会消耗掉很多的机器 CPU 周期。例如,如果 while 循环内没有程序代码,则执行此程序代码的执行绪可占据您机器上的大部份资源。事实上,它会吃掉很多处理时间,使得要传送 Web 服务要求和接收响应的基础程序代码因而延迟。因此,使用轮询时要小心。

如果您真的想要等到 Web 服务要求完成,您也许可以使用我们接下来要看的 WaitHandle 方式。不过,如果您有很多处理要完成,而且您只想要偶而检查一下 Web 服务呼叫是否结束,那么使用轮询并不是一个坏的方案。许多时候,应用程序会使用轮询与其中另一个异步方式的组合。例如,您可以同步发出 Web 服务呼叫,因为您有一些背景处理要执行,可是一旦背景处理执行完毕,您想要封锁直到 Web 服务完成为止。在此情况下,您可以在执行处理时偶而轮询一下,但是一旦背景处理完成,就使用 WaitHandle 来等候结果。

使用 WaitHandle

当您需要发出异步呼叫,但您不想释放目前在执行的执行绪,则 WaitHandle 很好用。例如,如果您从 ASP.NET 应用程序内发出异步 Web 服务呼叫,然后从处理 ASP.NET 应用程序的事件中返回,则您可能没有机会在传回给使用者的资料中并入 Web 服务呼叫中的资料。运用 WaitHandle,您就可以在发出 Web 服务呼叫之后做一些处理,然后封锁直到 Web 服务呼叫完成为止。如果您要从 ASP.NET 网页内发出多个 Web 服务呼叫,则 WaitHandle 特别有用。

WaitHandle 对象的存取权是由 BeginDelayedResponse 函式传回的 IAsyncResult 提供。下列程序代码显示使用 WaitHandle 方式的简单案例。

' 简单的 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 对象的 WaitOne 方法来等候这个控制代码。WaitHandle 类别中也有静态方法,叫作 WaitAll WaitAny。这两个静态方法以 WaitHandle 数组作为参数,它们可能在所有呼叫完成后才传回,或一有呼叫完成即传回,视您呼叫的函式而定。假设您叫作三个不同的 Web 服务。您可以异步地呼叫每一个服务,把 WaitHandle 放在每一个的数组中,然后呼叫 WaitAll 方法直到它们完成为止。这可让 Web 服务呼叫同时执行。如果您同步执行此动作,则无法并行地执行它们,导致执行时间变成三倍左右。

请注意,WaitOneWaitAll WaitAny 方法都有以逾时 (Timeout) 作为参数的选项。这可让您控制您要等候 Web 服务完成的时间。如果方法逾时,它们会传回 False 值。这可让您在重新等候之前执行更多处理,或让您有机会取消要求。

使用回呼

执行异步 Web 服务呼叫的第三个方式是使用回呼。如果您要发出很多同步 Web 服务呼叫,则使用回呼很有效率。它在 Web 服务呼叫的结果上执行背景处理很有帮助。不过,它是比其它两个方法更复杂的方式。在 Microsoft Windows® 应用程序中使用回呼特别适合,因为这可避免封锁 Window 的讯息执行绪。

回呼会在 Web 服务呼叫完成时导致回呼函式被呼叫。不过,在发出原始 BeginInvoke 呼叫的执行绪内容中,不可呼叫回呼函式。这会造成传送指令至 Windows Form 应用程序的控件时产生问题,因为那些指令必须是从特定执行绪呼叫,该执行绪为该特定 Window 处理讯息处理。还有,有一个方法可解决此问题。

下列程序代码显示一个简单案例,它使用回呼且 Web 服务呼叫的结果会显示在卷标控件中。

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,然后从子程序传回。请注意,本案例不传递 Nothing 作为第二个参数,而是传递 AsyncCallback 对象。基本上,这只是包装回呼函式地址的一个方式,叫作 ServiceCallbackServiceCallback 函式必须是一个子程序,它采取 IAsyncResult 类型的单一参数。SoapHttpClientProtocol 类别将呼叫此函式,并将提供对应完成的 Web 服务呼叫的 IAsyncResult 接口指针。同样地,我们使用 EndDelayedResponse 方法取得结果,但现在我们有一个小问题。

因为回呼可能不在 Window 的主要执行绪中,所以我们需要使用不同的 Invoke 方法来设定 Window 中的卷标文字。所有控件都有 Invoke 方法供您呼叫,以便呼叫一个在其主要讯息执行绪中执行的函式。在卷标控件上使用 Invoke 类似我们已经看过的其它一些内容。我们必须提供要它呼叫的函式地址给 Invoke,而且我们要在对象数组中包括要传给该函式的任何参数,作为第二个参数。在此范例中,我们必须宣告一个叫作 MyDelegate 的委派类型,来通知 Invoke 方法我们要它呼叫的函式语法。在此范例中,此函式叫作 DisplayResponse,且只有一个参数—从 Web 服务呼叫传回的字符串。将在卷标控件的适当执行绪内呼叫 DisplayResponse,而且它在设定应用程控项文字方面没有问题。

进阶议题

到目前为止,我们看过的范例都很直接。它们做了许多的假设,例如我们的 Web 服务呼叫成功,并简化为一次仅发出单一呼叫的事实。现在我们要看看有多个 Web 服务呼叫的案例。我们要看看如何处理这些呼叫可能产生的错误,并看看必要时如何取消呼叫。

使用 asyncState

我们先看一个范例,其中我们除了 Nothing 之外还传递其它项目作为 BeginDelayedResponse 呼叫的最后一个参数。如果您还记得,这是 asyncState 参数,它直接宣告为一个对象。现在我们要使用此参数来发出多个 Web 服务呼叫,使响应与适当的要求产生关联。

我现在要看的案例是对 DelayedResponse Web 方法发出三个不同的呼叫。然后它会在我的 Windows 应用程序的三个不同卷标控件中显示这些呼叫的结果。第一个呼叫的结果应该显示在第一个卷标,第二个呼叫的结果应该显示在第二个卷标,第三个呼叫的结果应该显示在第三个卷标。我要对每一个 Web 服务呼叫使用相同的回呼函式,而且为了判断哪一个卷标配哪一个呼叫,我会以 asyncState 参数传递卷标对象。为了制造更多的混淆,我让每一个呼叫要完成所花的时间随机化。其做法的程序代码显示如下。

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

透过传至回呼的 IAsyncResult 参数,传入 asyncState 参数的卷标对象可在我们的回呼函式中使用。我们也修改了前一个范例中的委派,来采用另一个参数。另一个参数同样是卷标,因此委派知道哪一个控件要设定文字。

也许您常常要传递更多信息,而不止是透过 asyncState 的单一对象。因为 asyncState 仅宣告为一个对象,因此也可以用它来传递复合信息,例如对象数组或您要使用的一些复合结构。在我们的案例中,我们只使用一个 Proxy 对象,但是在某些案例中,您可能有不同的 Proxy 对象给每一个 Web 服务呼叫。如果是这种情况,您也可以在 asyncState 资料中并入该 Proxy 对象。Web 服务呼叫的任何其它特定资料也可以考虑并入其中。

侦测错误

如果您习惯于发出同步 Web 服务呼叫,那么您可能也习惯透过以包装远程呼叫的 try...catch 区块来侦测错误。对异步呼叫做同样的事似乎令人感到困惑。我必须在 try...catch 区块中包装 BeginDelayedResponse EndDelayedResponse?也许我必须两个都包装!

其实,对于正常 SOAP 错误以及其它与传送相关的错误,我们只需要在 try...catch 区块中包装 EndDelayedResponse 呼叫。BeginDelayedResponse 呼叫不会产生错误,因为它会立刻传回,而不会等候是否有任何问题。当您在等待回呼被呼叫,或等待 Wait 呼叫解除封锁时,并不会随机发生错误。所发生的错误会透过您使用的机制来触发完成,机制可以是将 IsCompleted 属性设定为 True、或是触发 WaitHandle 或是呼叫回呼。唯有当您呼叫 EndDelayedResponse 时才会触发错误,它提供您关于失败的信息。

为了证明我的应用程序有可能失败,我在回呼函式中的 EndDelayedResponse 呼叫周围增加了一个 try...catch 区块。我顺便修改了失败的响应文字。

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

中止要求

在应用程序中使用异步要求的其中一个好处是,当呼叫完成时您不必锁定使用者接口。当然,如果您要让应用程序使用者继续与应用程序互动,那么您可以并入的一个极常见的功能,就是取消未完成要求的一个按钮。如果基于某种原因,Web 服务呼叫需要很长的时间才会完成,那么应该让使用者能够判断他们要等待多久。

中止要求很简单。使用 Proxy 类别上可用的 Abort 方法,那是在您使用 [加入 Web 参考] 选项时建立的。关于中止要求,请记住几件事。当您呼叫 Abort 方法时,任何未完成的要求仍然会完成,但它们完成时会有错误。这表示,如果您使用回呼,则仍然会对每一个未完成的要求呼叫您的回呼函式。呼叫 EndInvoke 方法 (或是我们案例中的包装函式 EndDelayedResponse) 时,会产生错误,指出基础连接已关闭。

繁衍执行绪以发出同步呼叫

还有另一个选项可用来解决异步呼叫的许多问题。那就是直接繁衍执行绪,并让该执行绪发出容易撰写程序代码的同步呼叫。在某些案例中这是合理的,但您应该了解关于此方式的一些议题。

首先,这会使 Web 服务呼叫程序代码更简单,但也可能会需要实作与我们使用过的部份程序代码一样复杂的程序代码,才能管理和在执行绪之间通讯。如果您要发出很多个 Web 服务呼叫,则您系统可能因为有太多个执行绪要管理而负担过重。以正常客户端应用程序而言,这可能不是什么大不了的事,但如果您要从 ASP.NET 网页发出呼叫,则您必须考虑有多少个使用者会同时使用您的系统。新增两个执行绪从 ASP.NET 网页执行 Web 服务呼叫,看起来负担不大,但如果有 200 个人同时使用同一张网页时会怎么样?现在您谈的是 400 个新执行绪,那负担一定很大。虽然执行异步呼叫的回呼机制使用额外执行绪来进行回呼,但它们重复使用执行绪集区,以提高回呼效率—同时避免有数百个执行绪同时执行的问题。

如果繁衍背景执行绪,使 Web 服务呼叫仍然在合理范围内,那么您应该考虑使用委派和处理执行绪集区来繁衍这些执行绪。然后您可以使用异步范例 (类似我们用于 Web 服务回呼的范例) 并发出函式呼叫来与执行绪上的控件互动,藉此呼叫这些方法。有关使用委派发出一般异步方法呼叫的其它详细信息,请参阅 Richard Grimes 的文章《.Net Delegates: Making Asynchronous Method Calls in the .Net Environment(英文)

结论

异步发出 Web 服务呼叫,可能是透过 HTTP .NET Framework 应用程序使用 Web 服务的好点子。大部份真正的应用程序会想要使用此功能来有效呼叫 Web 服务,而不让可能需要花费长时间的网络呼叫,造成应用程序被封锁。.NET Framework 在支持透过 HTTP 的异步 Web 服务呼叫的方式上很有弹性,它提供开发人员很多控制权,来决定他们要如何处理呼叫的完成。
作者:Matt Pow… | 文章来源:Microsoft Corporation | 更新时间:2007-10-26 11:13:49

  • 上一篇文章:

  • 下一篇文章:

  • 相关文章:
    j2se 嵌入式脚本抓取引擎
    NET Framework 2.0 Service Pack 2下载地址
    解析.NET中的6各大的安全错误
    .NET中Boolean,bool以及String,string之间的区别
    在VC 程序中自定义Vista事件日志
    .Net FrameWork 2.0 新增功能 Bulk Copy
    教程:.Net多线程和Windows Forms编程
    C++函数对象count_if
    语法C# 问关键字之:base、this
    C#中“+”的两种功能
    软件技术
    · 开发语言
    · Java技术
    · .Net技术
    · 数据库开发
    最新文章  
    ·.net技术 asp.net MVC3 Vi
    ·.net将视频文件格式转换成
    ·NET Framework 2.0 Servic
    ·如何动态修改winform的app
    ·asp,net软件结构设计和相关
    ·学习怎样使用ASP.NET中的虚
    ·,net基础类的学习:system
    ·.net学习:c#事件的深入分
    ·Facade Pattern学习总结
    ·C#如何设置或者获取目录的
    ·如何使用XSL来定义ASP.NET
    ·理解WCF Data Contract契约
    ·如何能在.net2.0开发的控件
    ·模式怎样使用,讲解模式的
    ·如何解决Menu菜单被frame遮
    关于我们 | 软件开发 | 下载试用 | 客服中心 | 联系我们 | 友情链接 | 网站地图 | 新疆电子地图 | RSS订阅
    版权所有 © 2016 新疆二域软件开发网 www.k8w.net All Rights Reserved 新ICP备14003571号
    新疆软件开发总机:0991-4842803、4811639.
    客服QQ:596589785 ;地址:新疆乌鲁木齐北京中路华联大厦A-5C 邮编:830000