我遇到了一个我能用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
答案 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类的新实例每次通话(并且它们将异步运行)。这意味着您还必须使用AddHandler
和RemoveHandler
在Calculate()中添加和删除事件处理程序。
有几种方法可以更新进度的用户界面,但建议使用BackgroundWorker.ProgressChanged
事件和BackgroundWorker.ReportProgress
方法。
使用BackgroundWorker.RunWorkerCompleted
事件作为回调触发器,报告计算完成的UI,从而触发表示结果所需的代码。这种方法消除了维护线程循环计算线程的需要 - 从而消除了对DoEvents()
的需求。它允许计算线程在其完成工作时通知其老板,而不是让老板检查工人的状态并一遍又一遍地睡觉。