我有一个VB.NET应用程序,可以创建设备监控线程。 MonitorThread是一个无尽的"循环,通过阻塞函数DeviceRead()
等待设备数据,然后使用数据更新表单控件。当设备暂停时,DeviceRead()
返回零,这会导致MonitorThread终止。这一切都很完美。
问题是:在FormClosing()
中,主线程暂停设备,然后调用Join()
等待MonitorThread终止,但Join()
永远不会返回,这会导致应用程序挂。从未到达MonitorThread末尾的断点,表明MonitorThread在某种程度上被饿死了。但是,如果我在DoEvents()
之前插入Join()
,那么一切都按预期工作。为什么DoEvents()
必须防止挂起,有没有更好的方法呢?
我的代码的简化版本:
Private devdata As DEVDATASTRUCT = New DEVDATASTRUCT
Private MonitorThread As Threading.Thread = New Threading.Thread(AddressOf MonitorThreadFunction)
Private Sub FormLoad(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
DeviceOpen() ' Open the device and start it running.
MonitorThread.Start() ' Start MonitorThread running.
End Sub
Private Sub FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles MyBase.Closing
DeviceHalt() ' Halt device. Subsequent DeviceRead() calls will return zero.
Application.DoEvents() ' WHY IS THIS NECESSARY? IF OMITTED, THE NEXT STATEMENT HANGS.
MonitorThread.Join() ' Wait for MonitorThread to terminate.
DeviceClose() ' MonitorThread completed, so device can be safely closed.
End Sub
Private Sub MonitorThreadFunction()
While (DeviceRead(devdata)) ' Wait for device data or halted (0). Exit loop if halted.
Me.Invoke(New MethodInvoker(AddressOf UpdateGUI)) ' Launch GUI update function and wait for it to complete.
End While
End Sub
Private Sub UpdateGUI()
' copy devdata to form controls
End Sub
答案 0 :(得分:0)
更新:
我已经提出了几个不依赖于DoEvents的挂起Join()
的解决方案。
在我的原始代码中Join()
由主线程调用,“拥有”UI,MonitorThread调用Invoke()
来更新UI。当MonitorThread调用Invoke()
时,它实际上是在UI消息队列上调度UpdateGUI()
的延迟执行,然后阻塞直到UpdateGUI()
完成。 DeviceRead()
和UpdateGUI()
共享一个数据缓冲区以提高效率。由于我不清楚的原因,只要主线程位于Join()
,MonitorThread就会被阻止 - 即使它可能被DeviceRead()
阻止,因此也不会在Invoke()
中等待。很明显,这会导致死锁,因为MonitorThread无法运行(从而终止),因此主线程永远不会从Join()
返回。
解决方案1:
避免从主线程调用Join()
。在FormClosing()
中,主线程启动TerminatorThread并取消表单关闭。由于主线程未被Join()
阻止,因此MonitorThread能够完成。同时,TerminatorThread在Join()
等待,直到MonitorThread完成,然后关闭设备并终止应用程序。
Private devdata As DEVDATASTRUCT = New DEVDATASTRUCT ' shared data buffer
Private Sub FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles MyBase.Closing
Dim t As Threading.Thread = New Threading.Thread(AddressOf TerminatorThread)
e.Cancel = True ' Cancel the app close.
DeviceHalt() ' Halt device.
t.Start() ' Launch TerminatorThread.
End Sub
Private Sub TerminatorThread()
MonitorThread.Join() ' Wait for MonitorThread to terminate.
DeviceClose() ' MonitorThread completed, so device can be safely closed.
Application.Exit() ' Close app.
End Sub
Private Sub MonitorThreadFunction()
While (DeviceRead(devdata)) ' Wait for device data or device halted (0).
Me.Invoke(New MethodInvoker(AddressOf UpdateGUI)) ' Launch UpdateGUI() and wait for it to complete.
End While
End Sub
Private Sub UpdateGUI()
' copy the shared devdata buffer to form controls
End Sub
解决方案2:
避免在监视器线程中等待UpdateGUI()
完成。这是通过调用BeginInvoke()
而不是Invoke()
来完成的,UpdateGUI()
仍会计划BeginInvoke()
的延迟执行,但不会等待它完成。但是,DeviceRead()
有一个不幸的副作用:它可能导致数据丢失,因为如果UpdateGUI()
在前一个延迟UpdateGUI()
完成之前返回,则共享的devdata缓冲区将被过早覆盖。解决方法是为Private Delegate Sub GUIInvoker(ByVal devdata As DEVDATASTRUCT)
Private Sub FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles MyBase.Closing
DeviceHalt() ' Halt device.
MonitorThread.Join() ' Wait for MonitorThread to terminate.
DeviceClose() ' MonitorThread completed, so device can be safely closed.
End Sub
Private Sub MonitorThreadFunction()
Dim devdata As DEVDATASTRUCT = New DEVDATASTRUCT ' private buffer
While (DeviceRead(devdata)) ' Wait for device data or device halted (0).
Me.BeginInvoke(New GUIInvoker(AddressOf UpdateGUI), devdata) ' Launch UpdateGUI() and return immediately.
End While
End Sub
Private Sub UpdateGUI(ByVal devdata As DEVDATASTRUCT)
' copy the unique devdata to form controls
End Sub
的每次调用创建设备数据的唯一副本,并将其作为参数传递。
xmltodict
答案 1 :(得分:-2)
我相信您应该使用BackgroundWorker,因为它与主线程一起终止。
请记住:您可能需要许多背景工作者,并且可以在其RunWorkerCompleted事件中附加一种召回顺序。
我认为这是您日常工作的最安全和最佳选择。在设置BackgroundWorker的CANCELATION之后,必须将CloseDevice放入FormClosing事件中(使用BG.cancelAsync)