为什么会阻塞? (带有事件和委托的 VB.NET 多线程)
我有一个功能,DLL 中的类会显示一个表单,要求用户在单击“重试”按钮之前清除打印机上的故障。用户只是点击重试而没有费心清除故障,所以我现在正在编写一个联锁: 调用表单上的按钮将被禁用,直到调用表单上的“启用”方法为止。
这是通过委托调用完成的,因为触发这些更改的事件来自在不同线程上运行的其他 dll。
表单的“enable”方法被连接到一个事件处理程序,处理来自不同线程(监视以太网 IO 服务器的线程)的事件。
我遇到的问题是“_Fault_StateChanged”事件永远不会触发。我怀疑原因是我在这里使用的“ShowDialog”和“DialogResult”技术,但我在该应用程序的其他地方使用了完全相同的技术。
任何建议都会很棒
请参阅下面的代码摘录:
MAIN CLASS 摘录
Public Class StatePrintHandler
Private WithEvents _RetryForm As frmRetryReject
Private Delegate Sub delShowRetryDialog()
Private Delegate Sub delResetEnable()
Private Sub InvokeResetEnable()
Dim del As delResetEnable
del = New delResetEnable(AddressOf ResetEnable)
del.Invoke()
End Sub
Private Sub InvokeRetryDialogue()
Dim del As delShowRetryDialog
del = New delShowRetryDialog(AddressOf ShowRetryDialog)
del.Invoke()
End Sub
Private Sub ShowRetryDialog()
_RetryForm = New frmRetryReject
_RetryForm.Prep()
_RetryForm.ShowDialog()
If (_RetryForm.DialogResult = Windows.Forms.DialogResult.OK) Then
Me._RetryForm.Visible = False
End If
End Sub
Private Sub ResetEnable()
If (Not IsNothing(_RetryForm)) Then
_RetryForm.ResetEnable()
Else
AuditTrail("Retry form not active, no action", True)
End If
End Sub
'Event handler for status change coming in on a different thread
Private Sub _Fault_StateChanged(ByVal sender As Object, ByVal e As Drivers.Common.DigitalSignalChangedEventArgs) Handles _fault.StateChanged
If (e.NewState) Then
AuditTrail("Labeller has faulted out during cycling", True)
Else
InvokeResetEnable()
End If
End Sub
End Class
RETRY FORM CLASS 摘录
Public Class frmRetryReject
Private Delegate Sub delEnable()
Public Event Complete()
Public Sub Prep()
Me.OK_Button.Enabled = False
End Sub
Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OK_Button.Click
Me.DialogResult = System.Windows.Forms.DialogResult.OK
Me.Close()
End Sub
Public Sub ResetEnable()
If (IsHandleCreated) Then
Dim params() As Object = {}
Me.Invoke(New delEnable(AddressOf InvokeEnable), params)
End If
End Sub
Private Sub InvokeEnable()
Me.OK_Button.Enabled = True
End Sub
End Class
针对 Daniel 的评论的其他详细信息
此处的代码不完整,它是摘录。故障对象是对外部库的订阅,并且是以太网 IO 服务器的处理程序。 当 IOServer 上的数字输入发生更改时,会触发 _fault StateChanged 事件。我知道以下几点: 我的跟踪文件显示信号变高,这会调用重试形式。 当重试屏幕仍然显示时,信号物理再次变为低电平。 ...但是事件不会触发
这就像应用程序无法为传入的事件提供服务,直到 ShowDialog/DialogResult 完成 - 但我对此感到困惑,因为我知道 .NET 2.0 中的 ShowDialog 没有阻止,我仍然应该能够为事件提供服务,并且在应用程序的其他地方使用了相同的模式。
有几点需要注意: MAIN CLASS 在运行时通过基于配置的反射动态实例化。 这是 VS2005 SP2
如果这有帮助的话,我会将整个类发布在另一个代码框中,但它可能会使场景拥挤......
谢谢 安迪
Imports System.IO
Imports ACS.Interfaces
Imports ACS.Pallet
Public Class StateCimPAKPrintHandler
Inherits StateBase
Private WithEvents _ioManager As ACS.Components.DigitalIOManager
Private _config As StateCimPAKPrintHandlerBootstrap
Private CONST_OutcomeOK As String = "OK"
Private CONST_OutcomeRetry As String = "Retry"
Private CONST_OutcomeException As String = "Exception"
Private WithEvents _busy As ACS.Drivers.Common.InputSignal
Private WithEvents _fault As ACS.Drivers.Common.InputSignal
Private WithEvents _print As ACS.Drivers.Common.OutputSignal
Private WithEvents _palletRelease As ACS.Drivers.Common.OutputSignal
Private _labellingInProgress As Boolean
Private _faulted As Boolean
Private _retryScreenInvoked As Boolean
Private WithEvents _timeout As System.Timers.Timer
Private WithEvents _faultTimer As System.Timers.Timer
Private WithEvents _RetryForm As frmRetryReject
Private Delegate Sub delShowRetryDialog()
Private Delegate Sub delResetEnable()
Private Sub InvokeResetEnable()
Dim del As delResetEnable
del = New delResetEnable(AddressOf ResetEnable)
del.Invoke()
End Sub
Private Sub InvokeRetryDialogue()
Dim del As delShowRetryDialog
del = New delShowRetryDialog(AddressOf ShowRetryDialog)
del.Invoke()
End Sub
Private Sub ShowRetryDialog()
_timeout.Stop()
_retryScreenInvoked = True
_RetryForm = New frmRetryReject
_RetryForm.Prep()
AuditTrail("Displaying Retry screen", True)
_RetryForm.ShowDialog()
If (_RetryForm.DialogResult = Windows.Forms.DialogResult.OK) Then
AuditTrail("User clicked RETRY LINE on the RETRY dialogue", True)
_retryScreenInvoked = False
Me._RetryForm.Visible = False
Me.SetOutcome(CONST_OutcomeRetry)
End If
End Sub
Private Sub ResetEnable()
If (Not IsNothing(_RetryForm)) Then
_RetryForm.ResetEnable()
Else
AuditTrail("Retry form not active, no action", True)
End If
End Sub
Public Sub New(ByVal Sequencer As ISequencer, ByVal ParentPlt As ACS.Interfaces.IPallet, ByVal Name As String)
MyBase.New(Sequencer, ParentPlt, Name)
_timeout = New System.Timers.Timer
_faultTimer = New System.Timers.Timer
_config = New StateCimPAKPrintHandlerBootstrap(Me._myIniFileName, Name)
_timeout.Interval = _config.CycleTimeoutMS
_faultTimer.Interval = 750
_retryScreenInvoked = False
_RetryForm = New frmRetryReject
Me._RetryForm.Visible = False
_ioManager = ACS.Components.DigitalIOManager.GetInstance
_busy = _ioManager.GetInput("Busy")
_fault = _ioManager.GetInput("CimPAKFault")
_print = _ioManager.GetOutput("Print")
_palletRelease = _ioManager.GetOutput("PalletRelease")
End Sub
Public Overrides Sub Kill()
_ioManager = Nothing
_RetryForm = Nothing
_busy = Nothing
_fault = Nothing
_print = Nothing
_timeout = Nothing
_faultTimer = Nothing
_pallet = Nothing
End Sub
Public Overrides Sub Execute()
AuditTrail("Pulsing Print Signal", True)
_print.PulseOutput(3000)
_labellingInProgress = True
_timeout.Start()
End Sub
Private Sub _busy_StateChanged(ByVal sender As Object, ByVal e As Drivers.Common.DigitalSignalChangedEventArgs) Handles _busy.StateChanged
_timeout.Stop()
AuditTrail("Busy signal changed to : " & e.NewState, True)
If (e.NewState) Then
_faulted = False
AuditTrail("CimPAK = Busy High", True)
_labellingInProgress = True
Else
AuditTrail("CimPAK = Busy Low", True)
AuditTrail("Wait 750 milliseconds for any faults", True)
_faultTimer.Start()
End If
End Sub
Private Sub _Fault_StateChanged(ByVal sender As Object, ByVal e As Drivers.Common.DigitalSignalChangedEventArgs) Handles _fault.StateChanged
AuditTrail("Fault signal changed to : " & e.NewState, True)
If (e.NewState) Then
If (_labellingInProgress = True) Then
AuditTrail("Labeller has faulted out during cycling", True)
_faulted = True
If (Not _retryScreenInvoked) Then
InvokeRetryDialogue()
End If
Else
AuditTrail("Labeller has faulted out between cycles, no action can be taken", True)
End If
Else
If (_retryScreenInvoked) Then
AuditTrail("Enable button on Retry screen", True)
InvokeResetEnable()
End If
_faulted = False
End If
End Sub
Private Sub _faultTimer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles _faultTimer.Elapsed
_faultTimer.Stop()
If (_faulted) Then
AuditTrail("System has faulted", True)
Else
AuditTrail("No fault occured, assume pallet is OK to release", True)
AuditTrail("CimPAK cycle complete", True)
_labellingInProgress = False
_palletRelease.PulseOutput(3000)
Me.SetOutcome(CONST_OutcomeOK)
End If
End Sub
Private Sub _timeout_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles _timeout.Elapsed
_timeout.Stop()
AuditTrail("Labeller print cycle timed out", True)
If (Not _retryScreenInvoked) Then
_retryScreenInvoked = True
InvokeRetryDialogue()
InvokeResetEnable()
End If
End Sub
End Class
#Region "Bootstrap"
Public Class StateCimPAKPrintHandlerBootstrap
Private Const CONST_CycleTimeoutMS As String = "CycleTimeoutMS"
Private _CycleTimeoutMS As Long
#Region "Properties"
Public ReadOnly Property CycleTimeoutMS() As Long
Get
Return _CycleTimeoutMS
End Get
End Property
#End Region
Public Sub New(ByVal IniFile As String, ByVal Name As String)
Try
Dim _cfgFile As String = Environ("ACSVAR") & "\" & IniFile
' Check to see if the CFG file exits
If File.Exists(_cfgFile) = False Then
Throw New Exception("Configuration file does not exist: " & _cfgFile)
Else
'Get values
_CycleTimeoutMS = ACS.Utility.Configuration.GetLong(_cfgFile, Name, CONST_CycleTimeoutMS)
End If
Catch ex As Exception
Throw
End Try
End Sub
End Class
#End Region
I have a feature whereby a class in a dll displays a form asking a user to clear a fault on a printer before clicking a button to say "Retry". The users have been just hitting retry without bothering to clear the fault so I am now coding an interlock:
The button on the invoked form is disabled until a call is made to an 'enable' method on the form.
This is done with delegate invocation as the events triggering these changes come from other dlls running on different threads.
The form's 'enable' method is wired into an EVENT HANDLER handling an event coming in from a different thread (one that monitors an ethernet IO Server).
The problem I have is that THE "_Fault_StateChanged" EVENT NEVER FIRES. I suspected the cause was the "ShowDialog" and "DialogResult" technique I have used here, but I have used this exact same technique elsewhere in this application.
Any suggestions would be great
See code extract below:
MAIN CLASS excerpt
Public Class StatePrintHandler
Private WithEvents _RetryForm As frmRetryReject
Private Delegate Sub delShowRetryDialog()
Private Delegate Sub delResetEnable()
Private Sub InvokeResetEnable()
Dim del As delResetEnable
del = New delResetEnable(AddressOf ResetEnable)
del.Invoke()
End Sub
Private Sub InvokeRetryDialogue()
Dim del As delShowRetryDialog
del = New delShowRetryDialog(AddressOf ShowRetryDialog)
del.Invoke()
End Sub
Private Sub ShowRetryDialog()
_RetryForm = New frmRetryReject
_RetryForm.Prep()
_RetryForm.ShowDialog()
If (_RetryForm.DialogResult = Windows.Forms.DialogResult.OK) Then
Me._RetryForm.Visible = False
End If
End Sub
Private Sub ResetEnable()
If (Not IsNothing(_RetryForm)) Then
_RetryForm.ResetEnable()
Else
AuditTrail("Retry form not active, no action", True)
End If
End Sub
'Event handler for status change coming in on a different thread
Private Sub _Fault_StateChanged(ByVal sender As Object, ByVal e As Drivers.Common.DigitalSignalChangedEventArgs) Handles _fault.StateChanged
If (e.NewState) Then
AuditTrail("Labeller has faulted out during cycling", True)
Else
InvokeResetEnable()
End If
End Sub
End Class
RETRY FORM CLASS excerpt
Public Class frmRetryReject
Private Delegate Sub delEnable()
Public Event Complete()
Public Sub Prep()
Me.OK_Button.Enabled = False
End Sub
Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OK_Button.Click
Me.DialogResult = System.Windows.Forms.DialogResult.OK
Me.Close()
End Sub
Public Sub ResetEnable()
If (IsHandleCreated) Then
Dim params() As Object = {}
Me.Invoke(New delEnable(AddressOf InvokeEnable), params)
End If
End Sub
Private Sub InvokeEnable()
Me.OK_Button.Enabled = True
End Sub
End Class
Additional detail in response to Daniel's comments
The code is incomplete here, it's an excerpt. The fault object is a subscription to an external library and is a handler for an ethernet IO server.
The _fault StateChanged event fires when a digital input on an IOServer changes. I know the following:
My trace files show the signal change high, this invokes the retry form.
The signal physically changes low again when the retry screen is still showing.
...but the event does not fire
It's as if the application cannot service the event coming in until the ShowDialog/DialogResult completes - but I am confused about this because I understood that the ShowDialog in .NET 2.0 did not block, I should still be able to service events, and have used this same pattern elsewhere in the app.
A couple of things to note:
The MAIN CLASS is instantiated dynamically at runtime via reflection based on configuration.
This is VS2005 SP2
I will post the entire class in another code box if this helps, but it may crowd the scene...
Thanks
Andy
Imports System.IO
Imports ACS.Interfaces
Imports ACS.Pallet
Public Class StateCimPAKPrintHandler
Inherits StateBase
Private WithEvents _ioManager As ACS.Components.DigitalIOManager
Private _config As StateCimPAKPrintHandlerBootstrap
Private CONST_OutcomeOK As String = "OK"
Private CONST_OutcomeRetry As String = "Retry"
Private CONST_OutcomeException As String = "Exception"
Private WithEvents _busy As ACS.Drivers.Common.InputSignal
Private WithEvents _fault As ACS.Drivers.Common.InputSignal
Private WithEvents _print As ACS.Drivers.Common.OutputSignal
Private WithEvents _palletRelease As ACS.Drivers.Common.OutputSignal
Private _labellingInProgress As Boolean
Private _faulted As Boolean
Private _retryScreenInvoked As Boolean
Private WithEvents _timeout As System.Timers.Timer
Private WithEvents _faultTimer As System.Timers.Timer
Private WithEvents _RetryForm As frmRetryReject
Private Delegate Sub delShowRetryDialog()
Private Delegate Sub delResetEnable()
Private Sub InvokeResetEnable()
Dim del As delResetEnable
del = New delResetEnable(AddressOf ResetEnable)
del.Invoke()
End Sub
Private Sub InvokeRetryDialogue()
Dim del As delShowRetryDialog
del = New delShowRetryDialog(AddressOf ShowRetryDialog)
del.Invoke()
End Sub
Private Sub ShowRetryDialog()
_timeout.Stop()
_retryScreenInvoked = True
_RetryForm = New frmRetryReject
_RetryForm.Prep()
AuditTrail("Displaying Retry screen", True)
_RetryForm.ShowDialog()
If (_RetryForm.DialogResult = Windows.Forms.DialogResult.OK) Then
AuditTrail("User clicked RETRY LINE on the RETRY dialogue", True)
_retryScreenInvoked = False
Me._RetryForm.Visible = False
Me.SetOutcome(CONST_OutcomeRetry)
End If
End Sub
Private Sub ResetEnable()
If (Not IsNothing(_RetryForm)) Then
_RetryForm.ResetEnable()
Else
AuditTrail("Retry form not active, no action", True)
End If
End Sub
Public Sub New(ByVal Sequencer As ISequencer, ByVal ParentPlt As ACS.Interfaces.IPallet, ByVal Name As String)
MyBase.New(Sequencer, ParentPlt, Name)
_timeout = New System.Timers.Timer
_faultTimer = New System.Timers.Timer
_config = New StateCimPAKPrintHandlerBootstrap(Me._myIniFileName, Name)
_timeout.Interval = _config.CycleTimeoutMS
_faultTimer.Interval = 750
_retryScreenInvoked = False
_RetryForm = New frmRetryReject
Me._RetryForm.Visible = False
_ioManager = ACS.Components.DigitalIOManager.GetInstance
_busy = _ioManager.GetInput("Busy")
_fault = _ioManager.GetInput("CimPAKFault")
_print = _ioManager.GetOutput("Print")
_palletRelease = _ioManager.GetOutput("PalletRelease")
End Sub
Public Overrides Sub Kill()
_ioManager = Nothing
_RetryForm = Nothing
_busy = Nothing
_fault = Nothing
_print = Nothing
_timeout = Nothing
_faultTimer = Nothing
_pallet = Nothing
End Sub
Public Overrides Sub Execute()
AuditTrail("Pulsing Print Signal", True)
_print.PulseOutput(3000)
_labellingInProgress = True
_timeout.Start()
End Sub
Private Sub _busy_StateChanged(ByVal sender As Object, ByVal e As Drivers.Common.DigitalSignalChangedEventArgs) Handles _busy.StateChanged
_timeout.Stop()
AuditTrail("Busy signal changed to : " & e.NewState, True)
If (e.NewState) Then
_faulted = False
AuditTrail("CimPAK = Busy High", True)
_labellingInProgress = True
Else
AuditTrail("CimPAK = Busy Low", True)
AuditTrail("Wait 750 milliseconds for any faults", True)
_faultTimer.Start()
End If
End Sub
Private Sub _Fault_StateChanged(ByVal sender As Object, ByVal e As Drivers.Common.DigitalSignalChangedEventArgs) Handles _fault.StateChanged
AuditTrail("Fault signal changed to : " & e.NewState, True)
If (e.NewState) Then
If (_labellingInProgress = True) Then
AuditTrail("Labeller has faulted out during cycling", True)
_faulted = True
If (Not _retryScreenInvoked) Then
InvokeRetryDialogue()
End If
Else
AuditTrail("Labeller has faulted out between cycles, no action can be taken", True)
End If
Else
If (_retryScreenInvoked) Then
AuditTrail("Enable button on Retry screen", True)
InvokeResetEnable()
End If
_faulted = False
End If
End Sub
Private Sub _faultTimer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles _faultTimer.Elapsed
_faultTimer.Stop()
If (_faulted) Then
AuditTrail("System has faulted", True)
Else
AuditTrail("No fault occured, assume pallet is OK to release", True)
AuditTrail("CimPAK cycle complete", True)
_labellingInProgress = False
_palletRelease.PulseOutput(3000)
Me.SetOutcome(CONST_OutcomeOK)
End If
End Sub
Private Sub _timeout_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles _timeout.Elapsed
_timeout.Stop()
AuditTrail("Labeller print cycle timed out", True)
If (Not _retryScreenInvoked) Then
_retryScreenInvoked = True
InvokeRetryDialogue()
InvokeResetEnable()
End If
End Sub
End Class
#Region "Bootstrap"
Public Class StateCimPAKPrintHandlerBootstrap
Private Const CONST_CycleTimeoutMS As String = "CycleTimeoutMS"
Private _CycleTimeoutMS As Long
#Region "Properties"
Public ReadOnly Property CycleTimeoutMS() As Long
Get
Return _CycleTimeoutMS
End Get
End Property
#End Region
Public Sub New(ByVal IniFile As String, ByVal Name As String)
Try
Dim _cfgFile As String = Environ("ACSVAR") & "\" & IniFile
' Check to see if the CFG file exits
If File.Exists(_cfgFile) = False Then
Throw New Exception("Configuration file does not exist: " & _cfgFile)
Else
'Get values
_CycleTimeoutMS = ACS.Utility.Configuration.GetLong(_cfgFile, Name, CONST_CycleTimeoutMS)
End If
Catch ex As Exception
Throw
End Try
End Sub
End Class
#End Region
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
尝试查看
Visual Basic,子线程阻塞主线程
多线程示例中的答案和
新线程仍然阻塞 UI -Thread
来自(我)对于模型线程的回答......这两个应该给你编写的代码来启动。
Try to look at
Visual Basic, Child Thread Blocking Main Thread
answer for in Multithreading example and
New Thread is still blocking UI-Thread
answer from ( me ) for In Model Threading.... Those 2 should give you writen code to start aswell.
我已经很长时间没有做过任何 Winforms 工作了,但是您是否以模式方式显示对话框?因为这可能是委托调用未到达的原因。
异步委托的工作方式是将调用详细信息序列化到框架内部的内存位置,然后将窗口消息发布到相关线程的顶级窗口。模态对话框的工作方式是进入消息处理循环,该循环窃取线程的所有消息,以便只有一个对话框可以响应。您可以看到这可能会发生冲突。
It's been a long time since I did any Winforms work, but are you showing the dialog modally? Because that could be the cause of the delegate invokation not arriving.
Asynchronous delegates work by serializing the invokation details into a memory location internal to the framework and then posting a window message to the top-level window for the relevant thread. Modal dialogs work by going into a message processing loop that steals all the messages for the thread so that only that one dialog can respond. You can see how this might conflict.