我读到过,不应该直接调用一个事件,而是使用
if (SomeEvent != null)
SomeEvent(this, null);
我应该要做什么。
SomeEventHandler temp = SomeEvent;
if (temp != null)
temp(this, null);
为什么会这样?第二个版本如何变得线程安全?最佳实践是什么?
我读到过,不应该直接调用一个事件,而是使用
if (SomeEvent != null)
SomeEvent(this, null);
我应该要做什么。
SomeEventHandler temp = SomeEvent;
if (temp != null)
temp(this, null);
为什么会这样?第二个版本如何变得线程安全?最佳实践是什么?
在我看来,其他答案忽略了一个关键细节——委托(和因此而来的事件)是不可变的。这意味着订阅或取消订阅事件处理程序并不只是简单地添加/删除列表,而是用具有额外项(或少一项)的新列表替换它。
由于引用是原子性的,这意味着在执行以下操作时:
var handler = SomeEvent;
现在你拥有一个不可变的实例,即使在下一皮秒另一个线程取消订阅(导致实际事件字段变为null
),它也无法更改。
因此,您需要测试null并调用它,一切都很好。当然,请注意仍然存在一个混乱的情况,即事件在认为一皮秒前已取消订阅的对象上被引发!
lock
)区域(旧版编译器)。 - Marc Gravell事件实际上是一个委托列表的语法糖。当您调用事件时,它实际上是在迭代该列表并使用传递的参数调用每个委托。
线程的问题是它们可能会通过订阅/取消订阅来添加或删除此集合中的项。如果它们在您迭代集合时执行此操作,则会引发问题(我认为会抛出异常)。
意图是在迭代列表之前复制列表,这样您就可以受到对列表的更改的保护。
注意:现在可能会在取消订阅后仍调用您的侦听器,因此您应确保在侦听器代码中处理此情况。
最佳实践是使用第二种形式。原因是在“if”测试和调用之间,另一个线程可能会将SomeEvent
设置为null或更改它。
SomeEvent
的操作是原子性的,即要么全部执行完毕,要么不执行。因此,由于temp
是局部变量,它不能在该线程之外被修改。 - user7116