.NET移除事件处理程序并销毁调用的事件

3
在我的应用程序的主窗体上,我有一个事件处理程序,它是由硬件设备生成的,因此从与UI不同的线程调用。 为了与GUI线程同步,在调用事件时,我使用Form.BeginInvoke()。 然后,呼叫排队在消息循环中。
当用户需要关闭窗体时,在关闭之前,我会删除事件的处理程序,但似乎调用的请求仍可能被调用。 如果处理事件的例程使用一些在它被调用时不再可用的信息,我可能会遇到问题。
例如:
Private MyDevice as New SomeDevice()
Private MyGlobalVar as MyVarType

Public Sub OnDeviceEvent()

   If InvokeRequired Then
      BeginInvoke(Sub() OnDeviceEvent())
      Return
   End If

   If MyGlobalVar.Field = 0 then
    'do something
   end if

End Sub

Public Sub PrepareToCloseForm()

   'removes the handler of the event
   RemoveHandler MyDevice.DeviceEvent, AddressOf OnDeviceEvent

   MyGlobalVar = Nothing 

End Sub

在运行 PrepareToCloseForm() 后,有时我会在下面这行代码中遇到对象 Null 错误:
If MyGlobalVar.Field = 0 then

我可以在使用变量之前进行空值检查,但是由于我有许多其他事件,我希望有一个更优雅的解决方案。如何确保在移除处理程序后不会发生调用?

在删除所有处理程序之前,是否应该调用DoEvents以处理挂起的消息?


当你移除Eventhandler后,它将不再被执行。但是在DeveiceEvent被触发后、移除处理程序之前并且访问MyGlobalVar时,可能存在“竞态条件”的情况,因为此时变量已经设置为Nothing。全局变量的目的是什么?您可以考虑传递值给处理程序,而不是使用全局变量。根据您当前的设计,检查null将是最简单的解决方案 - 您可以引入返回所需值并在该方法中检查null的方法。 - Fabio
根据Eric Lippert在他的博客文章中所述:https://blogs.msdn.microsoft.com/ericlippert/2009/04/29/events-and-races/ "...即使在事件取消订阅后仍被调用,事件处理程序也必须具有鲁棒性。" - Craig
1个回答

3

您遇到的问题是,如果关闭了该对象并将MyGlobalVar设置为Nothing,则即使OnDeviceEvent检查它,仍然可能在您仍在尝试使用它的任何时间变为空。为了解决这个问题,您可以在访问MyGlobalVar时放置一个SyncLock。

Private MyDevice as SomeDevice()
Private MyGlobalVar as MyVarType
Private SyncLockObject as New Object

Public Sub OnDeviceEvent()

   If InvokeRequired Then
      BeginInvoke(Sub() OnDeviceEvent())
      Return
   End If

   SyncLock SyncLockObject
      If MyGlobalVar IsNot Nothing Then
         If MyGlobalVar.Field = 0 then
          'do something
        End If
      End If
  End SyncLock

End Sub

Public Sub PrepareToCloseForm()

   'removes the handler of the event
   RemoveHandler MyDevice.DeviceEvent, AddressOf OnDeviceEvent

   SyncLock SyncLockObject
      MyGlobalVar = Nothing 
   End SyncLock

End Sub

这样,当OnDeviceEvent访问MyGlobalVar时,您不能将其设置为无效值,而且在PrepareToCloseForm将其设置为无效值时,OnDeviceEvent也无法访问它。


但是如果我理解正确的话,这并不能解决我所描述的问题。你看,由于消息队列的原因,被调用的方法可能在 Synclock 已经退出 PrepareToCloseForm 之后被调用,并且它将能够正常获取和进入锁,但是在事件处理程序中仍然会出现空引用错误。 - Giovani Luigi
哦,抱歉。我在SyncLock中添加了一个检查,以确保它不是空的。 - dwilliss
但是在PrepareToCloseForm方法中将MyGlobalVar设置为Nothing后,事件处理程序仍然可以访问它。添加空值检查正是OP试图避免的,并要求我们采用不同的方法。 - Fabio
SyncLock 确保这不会发生。当事件处理程序在 SyncLock 内部时,关闭方法无法将其设置为 Nothing,反之亦然。 - dwilliss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接