事件处理程序不是线程安全吗?

Dan*_*iel 29 .net c# events multithreading thread-safety

所以我已经阅读过,而不是直接调用事件

if (SomeEvent != null)
   SomeEvent(this, null);
Run Code Online (Sandbox Code Playgroud)

我应该这样做

SomeEventHandler temp = SomeEvent;
if (temp != null)
    temp(this, null);
Run Code Online (Sandbox Code Playgroud)

为什么会这样?第二个版本如何变得线程安全?什么是最佳做法?

Mar*_*ell 32

IMO,其他答案错过了一个关键细节 - 委托(因此事件)是不可变的.这样做的意义在于,订阅或取消订阅事件处理程序不会简单地将/追加/删除到列表中 - 而是列表替换为新的列表,其中包含额外(或少一个)项目.

由于引用是原子的,这意味着您可以:

var handler = SomeEvent;
Run Code Online (Sandbox Code Playgroud)

你现在有一个无法改变的严格实例,即使在下一个皮秒中另一个线程取消订阅(导致实际事件字段变为null).

所以你测试null并调用它,一切都很好.当然,需要注意的是有还是事件的混乱场景被提出的是认为它前退订皮秒的对象!


Nig*_*rne 14

事件在代表列表中真的是语法糖.当您调用该事件时,这实际上正在迭代该列表并使用您传递的参数调用每个委托.

线程的问题在于它们可能通过订阅/取消订阅来添加或删除此集合中的项目.如果他们在迭代集合时执行此操作,则会导致问题(我认为会抛出异常)

目的是在迭代之前复制列表,这样就可以防止对列表进行更改.

注意:现在,即使您取消订阅也可以调用您的侦听器,因此您应该确保在侦听器代码中处理此问题.

  • 实际上,当您遍历集合时,它不会导致问题.这里的代表是不可改变的.唯一的问题是在检查是否有人订阅并实际调用事件处理程序之前的瞬间.一旦开始执行这些操作,任何后台更改都不会影响当前的调用. (2认同)

Mit*_*eat 5

最佳实践是第二种形式.原因是另一个线程可能SomeEvent在' if'测试和调用之间为null或更改.

  • 读取是`SomeEvent`是原子的,即它一直发生或什么也没发生.因此,`temp`不能在该线程之外被修改,因为它是本地的. (2认同)