【问题标题】:What happens if C# event handler disposed and I call it?如果 C# 事件处理程序被释放并且我调用它会发生什么?
【发布时间】:2011-11-30 21:52:38
【问题描述】:

这个问题是对C# Events and Thread Safety 问题(我不是那个问题的作者)和 Eric Lippert Events and Races 的相关博客文章的后续。关于 SO 还有其他类似的问题,但没有一个人真正考虑过这种情况,普遍的共识是,只要您退订,您就是安全的,但我不认为这一直都是真的。

根据 SO question 和博客中的讨论,应该使用的模式类似于:

var ev = NotifyPropertyChanged;
if (ev != null)
    ev(this, new PropertyChangedEventArgs("Foo"));

但是如果出现以下情况怎么办:
1)我订阅了一个监听器:

mytype.NotifyPropertyChanged += Handler; // Handler is instance method in SomeObject class

2) 我(或运行时,由于范围界定)处置 SomeObject,其中包含侦听器并取消订阅侦听器,大约在发生属性通知的同时。

3) 虽然由于时间很短,这不太可能发生,但理论上有可能因为 ev 保留了不再存在的旧订阅者,它会在不再存在的对象。

根据 Eric Lippert 的说法,“即使在取消订阅事件后,事件处理程序也必须在被调用时保持稳健”。但是如果处理程序被取消订阅并处置,它就不能再处理调用了。处理这种情况的正确方法是什么?

将 (1) 中的代码包装在 try-catch 中?应该捕获什么异常? ObjectDisposedException 似乎很可能发生,但我认为不是唯一可能发生的情况。

【问题讨论】:

  • 我有点想知道是否可以采用一种允许在事件处理程序中执行此操作的模式:if(Disposed) { /*get reference to caller's event and unsubscribe this object*/ 因为这种情况是内存泄漏的常见来源,其中有人将引用为空一个对象但忘记取消订阅它的处理程序。
  • 这个问题令人困惑。 “disposed”是指有人在对象上调用了 Dispose(),或者它是由垃圾收集器收集的,还是由垃圾收集器完成的?

标签: c# .net events thread-safety


【解决方案1】:

我相信你的意思是说一个已经被 GC 处理过的对象,而不是处置。那不可能发生; MultiCastDelegateEventHandler)通过其订阅的方法维护对对象的引用,即,在删除处理程序之前无法对它进行 GC。


Dispose()与方法不可用无关,它是一种用于清理原生资源的模式,即GC无法处理的资源。

对象本身仍然存在并且运行良好,但如果您调用依赖于该本地资源的方法,它可能会抛出异常(当然取决于实现。关键是对象仍然存在,方法也是如此)。

当您致电Dispose() 时,没有什么神奇的事情发生。我可以轻松地创建一个实现IDisposable 并具有完全空的Dispose() 方法的类。随心所欲地调用它,它什么都不做,也不会以任何方式改变对象的状态。

【讨论】:

  • 澄清一下,并不是我相信 Dispose 会做某事,而是 GC 会运行。我完全忘记了该事件将充当强根并阻止该对象上的 GC。
【解决方案2】:

Ed S. 所说的一切都是准确的。我想详细说明一下,您将看到的行为将取决于函数/事件处理程序的实现。它可能会或可能不会抛出异常,它的行为可能会或可能不会奇怪。

Dispose 不会做任何使对象无法访问的神奇操作。 Dispose 通常只做一些事情,例如如果其中一个字段/属性是文件句柄,那么它会依次调用该文件句柄上的 dispose 以立即释放资源(请参阅http://blogs.msdn.com/b/kimhamil/archive/2008/11/05/when-to-call-dispose.aspx

如果事件处理程序试图对该文件执行某些操作,则可能会引发异常。

虽然该事件处理程序的代码可能只处理整数和字符串等成员字段,因此可能表现正常。

编写该类/函数的人也可能有一个明确的if(Disposed){throw blah; },因此如果您尝试调用该函数,它会抛出一个异常,告诉您该操作在已处置的实例上无效。

取消订阅事件很重要,否则您的对象将永远不会被 GC。

【讨论】:

    【解决方案3】:

    我相信在大多数情况下,您不应该捕获订阅者抛出的任何异常。真的不能期望您处理客户端代码可能引发的所有可能的异常。总之,我认为这里应该应用快速失败的原则。

    【讨论】:

    • 没错,这是拥有处理程序的对象而不是引发事件的对象的问题。由事件处理程序正确处理。
    【解决方案4】:

    由对象决定如果它已经被 Disposed 应该发生什么(它仍然可以在“Disposed”状态下执行代码,因为 Dispose 只是你自愿调用来告诉对象清理自己的东西。)我之前在应用程序关闭期间已经看到这导致错误,其中在清理对象时正在另一个线程上处理事件;未正确编写事件处理程序以防止这种竞争条件。值得关注。

    【讨论】:

      【解决方案5】:

      此代码始终有效...

      var ev = NotifyPropertyChanged;
      if (ev != null)     
          ev(this, new PropertyChangedEventArgs("Foo")); 
      

      ... 即使另一个线程取消订阅第一行和最后一行之间的事件处理程序。这是因为委托是不可变的。每次添加或删除事件处理程序时,都会创建一个新的多播委托。因此,您可以将它们视为值类型。所以ev 在第一行被赋值后就永远不会改变了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-20
        • 2011-12-31
        相关资源
        最近更新 更多