如果C#事件处理程序处理并且我调用它会发生什么?

时间:2011-11-30 21:52:38

标签: c# .net events thread-safety

这个问题是对C# Events and Thread Safety问题(我不是那个问题的作者)和Eric Lippert Events and Races的相关博客文章的跟进。关于SO还有其他类似的问题,但是没有人真正考虑过这个案例,普遍的共识是,只要你取消订阅就是安全的,但我不相信这一直是真的。

根据SO问题和博客中的讨论,应该使用的模式如下:

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

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

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

2)I(或运行时,由于作用域)在几乎同时发生属性通知的同时处理包含侦听器并取消订阅侦听器的SomeObject。

3)虽然由于非常短的时间段可能会发生这种情况,但理论上可能因为 ev 保留了不再存在的旧订阅者,它将调用一个函数不再存在的对象。

根据Eric Lippert的说法,“即使在事件取消订阅后,事件处理程序也必须是健壮的”。但如果处理程序取消订阅并处置,则无法再处理该调用。处理这种情况的正确方法是什么?

在try-catch中包装(1)中的代码?应该抓住什么例外?我认为ObjectDisposedException似乎很可能,但不是唯一可能发生的事情。

5 个答案:

答案 0 :(得分:10)

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


Dispose()与无法使用的方法无关,它是用于清理本机资源的模式,即GC无法处理的资源。

对象本身仍然存活且很好,但是如果你调用一个依赖于本机资源的方法,它可能会抛出异常(取决于当然的实现。重点是对象仍然存在,方法也是如此)。

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

答案 1 :(得分:2)

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

Dispose没有任何神奇的东西可以使对象无法访问。处理通常只是做一些事情,比如如果其中一个字段/属性是File句柄,那么它会依次调用dispose来释放资源(参见http://blogs.msdn.com/b/kimhamil/archive/2008/11/05/when-to-call-dispose.aspx

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

虽然该事件处理程序的代码可以处理成员字段(如int和字符串),因此可能正常运行。

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

取消订阅活动非常重要,否则您的对象将永远不会被GC。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

此代码始终有效......

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

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