想要在不使用Application.DoEvents的情况下多次调用相同的BackgroundWorker

时间:2010-03-04 21:04:25

标签: vb.net web-services backgroundworker

我遇到了一个我能用Application.DoEvents修复的问题,但是不想留下它,因为它可能会引入各种令人讨厌的问题。

背景: 我们的应用程序主要是一个桌面应用程序,可以调用Web服务。我们控制一切,但不会认真考虑整体系统设计的变化。其中一个调用,Calculate,经常使用,偶尔可能需要几分钟来处理所有数据以返回有效结果。

以前对Calculate的调用是同步完成的,因此会阻止UI让用户怀疑应用程序是否已冻结等等。我已成功将所有长时间等待调用移至BackgroundWorker然后变得简单等待屏幕循环显示“计算...”动画消息。

现在,当我们的UI代码尝试在第一个完成之前再次调用计算例程时出现问题。我会得到一个“这个BackgroundWorker当前很忙,不能运行多个实例......”的消息。我认为应该由resetEvent.WaitOne()调用。事实并非如此,我想也许控制对整个例程的访问的另一个事件会有所帮助,所以我添加了calcDoneEvent。这仍然无法解决问题,但会导致它在第二次调用Calculate的calcDoneEvent.WaitOne()调用时无限期地阻塞。然后我一时兴起将Application.DoEvents添加到Calculate和viola的底部,问题解决了。

我不想留下那个.DoEvents因为我读过它可能会导致以后很难追查的问题。有没有更好的方法来处理这种情况?

提前致谢..

Private WithEvents CalculateBGW As New System.ComponentModel.BackgroundWorker
Dim resetEvent As New Threading.AutoResetEvent(False)
Dim calcDoneEvent As New Threading.AutoResetEvent(True)

Public Sub Calculate()

    calcDoneEvent.WaitOne() ' will wait if there is already a calculate running.'
    calcDoneEvent.Reset()

    ' setup variables for the background worker'

    CalculateBGW.RunWorkerAsync() ' Start the call to calculate'

    Dim nMsgState As Integer = 0
    ' will block until the backgorundWorker is done'
    Do While Not resetEvent.WaitOne(200) ' sleep for 200 miliseconds, then update the status window'
        Select Case nMsgState
            Case 1
                PleaseWait(True, vbNull, "Calculating.   ")
            Case 2
                PleaseWait(True, vbNull, "Calculating..  ")
            Case 3
                PleaseWait(True, vbNull, "Calculating... ")
            Case 4
                PleaseWait(True, vbNull, "Calculating....")
            Case Else
                PleaseWait(True, vbNull, "Calculating    ")
        End Select
        nMsgState = (nMsgState + 1) Mod 5
    Loop

    PleaseWait(False, vbNull) 'make sure the wait screen goes away'

    calcDoneEvent.Set() ' allow another calculate to proceed'
    Application.DoEvents() ' I hate using this here'
End Sub

Private Sub CalculateBGW_DoWork(ByVal sender As System.Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) Handles CalculateBGW.DoWork
    Try
        'make WS Call, do data processing on it, can take a long time..'
        'No Catch inside the DoWork for BGW, or exception handling wont work right...'
        'Catch'
    Finally
        resetEvent.Set() 'unblock the main thread'
    End Try
End Sub

Private Sub CalculateBGW_RunWorkerCompleted(ByVal sender As Object, _
    ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles CalculateBGW.RunWorkerCompleted

    'If an error occurs we must check e.Error prior to touching e.Result, or the BGW' 
    'will possibly "eat" the exception for breakfast (I hear theyre tasty w/ jam)'
    If Not (e.Error Is Nothing) Then

        'If a Web Exception timeout, retry the call'
        If TypeOf e.Error Is System.Net.WebException And _
            e.Error.Message = "The operation has timed out" And _
            intRetryCount < intRetryMax Then

            ' Code for checking retry times, increasing timeout, then possibly recalling the BGW'

            resetEvent.Reset()
            CalculateBGW.RunWorkerAsync() 'restart the call to the WS'
        Else
            Throw e.Error ' after intRetryMax times, go ahead and throw the error up higher'
        End If
    Else
        Try

            'normal completion stuff'

        Catch ex As Exception
            Throw
        End Try
    End If

End Sub

1 个答案:

答案 0 :(得分:4)

你宣布:

Private WithEvents CalculateBGW As New System.ComponentModel.BackgroundWorker
Dim resetEvent As New Threading.AutoResetEvent(False)
Dim calcDoneEvent As New Threading.AutoResetEvent(True)

作为包含类的私有字段。请注意,这样,对RunWorkerAsync()的所有调用都被引用到BackgroundWorker类的相同对象实例(即,对同一对象)。这就是它“忙”的原​​因。此代码构建为在给定时间仅保留一个BackgroundWorker

如果您希望允许UI代码在需要时调用Calculate()方法,则应将CalculateBGW声明为Calculate()方法中的局部变量,从而创建BackgroundWorker类的新实例每次通话(并且它们将异步运行)。这意味着您还必须使用AddHandlerRemoveHandler在Calculate()中添加和删除事件处理程序。

有几种方法可以更新进度的用户界面,但建议使用BackgroundWorker.ProgressChanged事件和BackgroundWorker.ReportProgress方法。

使用BackgroundWorker.RunWorkerCompleted事件作为回调触发器,报告计算完成的UI,从而触发表示结果所需的代码。这种方法消除了维护线程循环计算线程的需要 - 从而消除了对DoEvents()的需求。它允许计算线程在其完成工作时通知其老板,而不是让老板检查工人的状态并一遍又一遍地睡觉。