65.9K
CodeProject 正在变化。 阅读更多。
Home

异步处理函数和 Web 服务调用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (18投票s)

2004 年 7 月 15 日

CPOL

6分钟阅读

viewsIcon

161359

downloadIcon

2088

如何异步处理函数和 Web 服务调用。

Sample Image - backgroundworker.png

引言

我一直在进行的一个项目要求实现异步处理,特别是针对耗时较长的 Web 服务调用。因此,我开始研究微软异步处理应用程序块,并阅读有关多线程等文章。我偶然发现的一篇文章是 Roy Osherove 关于《在 .NET 1.1 中实现 .NET 2.0 的 Background Worker 进程》的博客。这似乎正是我所需要的,特别是当我阅读了 Joval Löwy 的原始文章后,但有三点不同。它没有提到如何使用它来调用 Web 服务;它只能传递一个参数,因此对于执行更复杂的函数,您需要将参数作为数组或哈希表传递;而且它是用 C# 编写的,而我的项目是 VB.NET,所以我无法将其放入我的代码中。是的,我知道我可以在 VB.NET 中引用 C# 程序集或项目,但为了我的解决方案更清晰,我需要将整个代码移植到 VB.NET。

背景

在 .NET 中,异步调用模式已经被探索了很多次,并且在 David Hill 的《为 Windows Forms 应用程序创建简化的异步调用模式》等文章中有详细描述。

Whidbey 将在 System.ComponentModel 命名空间中引入一个名为 BackgroundWorker 的组件,它封装了异步调用的复杂性,并提供了一个简单的组件,可以从代码调用,也可以拖放到设计器中的 Form 上。它公开了用于执行异步函数、更新进度、取消执行和完成执行的方法和事件。

如上所述,Joval Löwy 和 Roy Osherove 已经用 C# 为 .NET v1.1 实现了一个该类的版本,这对于许多人来说已经足够了。我已经将其移植到了 VB.NET,并进行了一些调整,以便在进行子类化时抛出与 Whidbey 版本类似的异常。然后我对其进行了扩展,并演示了如何使用它来异步调用 Web 服务。

移植到 VB.NET

这并非没有遇到一些奇怪的问题。

引发事件的最佳实践告诉我们,在引发事件之前,我们应该始终测试事件是否不为 null。也就是说,事件必须有处理程序。所有文章都给出了类似这样的代码示例

         if(SomeEvent != null)
         {
            SomeEvent(this, args);
         }

BackgroundWorker 的 C# 实现都使用了这种标准。但在 VB.NET 中该如何实现呢?如果您尝试执行语句 SomeEvent != Nothing,您会收到错误:'Public Event SomeEvent(sender As Object, e As System.EventArgs)' is an event, and cannot be called directly. Use a 'RaiseEvent' statement to raise an event. 这没什么用!

事实证明,在 VB.NET 中,要访问派生的代理对象以测试它是否为 Nothing,只需在事件名称的末尾添加“Event”。但请不要在 IntelliSense 中查找它,因为它不会在那里!生成的 VB.NET 代码是

         if Not SomeEventEvent is Nothing Then
            SomeEventEvent(Me, args)
         End If

为什么我使用了 SomeEventEvent(Me, args) 而不是 RaiseEvent SomeEvent(Me, args)

委托的默认方法是 Invoke 方法,而 VB.NET 中的 RaiseEvent 只是在该委托上调用此方法,这使得我的代码语言无关性更强。这更多是个人偏好。

注意:源代码下载包含 VB.NET 移植版本和我创建的子类。

子类化 BackgroundWorker

这有两个目的。首先,当 Whidbey 发布时,切换到调用本机 BackgroundWorker 将只需要在子类中进行八处代码更改,而不是在我的整个应用程序中进行大量更改。其次,它使我们能够向 BackgroundWorker 添加功能,在这种情况下,就是能够向异步函数传递多个参数。

首先,三个事件参数类,DoWorkEventArgsProgressChangedEventArgsRunWorkerCompletedEventArgs 都必须进行子类化,以及它们相关的处理程序委托。然后 BackgroundWorker 类本身就可以进行子类化。

我们必须处理底层事件并抛出我们子类化的版本。

    Public Shadows Event DoWork As DoWorkEventHandler
    Public Shadows Event ProgressChanged As ProgressChangedEventHandler
    Public Shadows Event RunWorkerCompleted As RunWorkerCompletedEventHandler

DoWork 事件是最简单的,尽管您需要小心地将子类化的事件参数重新分配给原始事件参数。

    Private Sub BackgroundWorker_DoWork(ByVal sender As Object, _
        ByVal e As VS2005.DoWorkEventArgs) Handles MyBase.DoWork
        If Not DoWorkEvent Is Nothing Then
            Dim args As New DoWorkEventArgs(e.Argument)
            DoWorkEvent(sender, args)
            e.Cancel = args.Cancel
            If Not e.Cancel Then
                e.Result = args.Result
            End If
        End If
    End Sub

然而,ProgressChanged 事件必须调用我们移植的 BackgroundWorker 中的 ProcessDelegate 函数的副本。

    Private Sub BackgroundWorker_ProgressChanged(ByVal sender As Object, _
        ByVal e As VS2005.ProgressChangedEventArgs) Handles MyBase.ProgressChanged
        If Not ProgressChangedEvent Is Nothing Then
            Me.ProcessDelegate(ProgressChangedEvent, Me, _
                New ProgressChangedEventArgs(e.ProgressPercentage, Nothing))
        End If
    End Sub

这是因为 Whidbey 版本没有这个方法,所以如果我们调用基类函数,那么我们将失去轻松升级的能力。

然而最糟糕的是 RunWorkerCompleted 事件。RunWorkerCompletedEventArgs 类在访问 Result 属性且该属性将返回 null 时会抛出异常,我们必须解决这个问题才能将事件参数传递到我们子类化的事件参数中。微软已经为 Whidbey 更改了委托的一些内部工作方式。

在 .NET 1.1 中,如果我们不处理异常,它会导致回调委托被调用两次。第二次,会因为无法调用 EndInvoke 两次而抛出新的异常。

在 Whidbey 中,异常会沿着调用堆栈冒泡,直到被处理。

为了解决这个问题,我们只在未取消或未出错的情况下读取 Result 属性的值。

    Private Sub BackgroundWorker_RunWorkerCompleted(ByVal sender As Object, _
        ByVal e As VS2005.RunWorkerCompletedEventArgs) _
        Handles MyBase.RunWorkerCompleted
        If Not RunWorkerCompletedEvent Is Nothing Then
            Dim result As Object = Nothing
            If e.Cancelled = False AndAlso e.Error Is Nothing Then
                result = e.Result
            End If
            Me.ProcessDelegate(RunWorkerCompletedEvent, Me, _
                New RunWorkerCompletedEventArgs(result, e.Error, e.Cancelled))
        End If
    End Sub

最后,我们想扩展这个类。我们只需重载 RunWorkerAsync 方法,并向其传递一个类型为 Object()ParamArray

    Public Overloads Sub RunWorkerAsync(ByVal ParamArray arguments As Object())
        MyBase.RunWorkerAsync(arguments)
    End Sub

Using the Code

使用此类进行基本的异步调用很简单,只需连接 DoWorkProgressChangedRunWorkerCompleted 事件,然后调用 RunWorkerAsync 方法,就像我在演示应用程序中展示的那样。

然而,进行异步 Web 服务调用有点奇怪。您以与正常情况相同的方式连接事件,并在 DoWork 事件处理程序中,**同步**调用 Web 服务。这正是调用代理类自动生成的 BeginWebMethod/EndWebMethod 方法时所发生的情况。它会创建一个新线程并在那里进行常规的同步调用。

当然,异步线程在调用期间将被锁定,因此您需要在返回时检查是否有取消挂起。您的 RunWorkerCompleted 处理程序必须确保仅当 cancelled 为 False 时才执行任何操作。

在数据开始返回之前,无法监视 Web 服务调用的进度,届时您可以监视接收数据流的进度。然而,为了演示的目的,我只是设置了一个计时器,每秒更新十次进度条。这足以让用户认为正在发生一些事情,而确实也正在发生,只是不在他们的机器上!

历史

  • 2004 年 7 月 15 日:初始版本
© . All rights reserved.