C# 事件处理程序不是线程安全的?

C# 事件处理程序不是线程安全的?,c#,.net,multithreading,events,thread-safety,C#,.net,Multithreading,Events,Thread Safety,所以我读了这篇文章,而不是直接用 if (SomeEvent != null) SomeEvent(this, null); 我应该做什么 SomeEventHandler temp = SomeEvent; if (temp != null) temp(this, null); 为什么会这样?第二个版本如何成为线程安全的?最佳实践是什么?最佳实践是第二种形式。原因是另一个线程可能会在“if”测试和调用之间为null或更改SomeEvent。是一篇关于.NET事件和线程竞争条件

所以我读了这篇文章,而不是直接用

if (SomeEvent != null)
   SomeEvent(this, null);
我应该做什么

SomeEventHandler temp = SomeEvent;
if (temp != null)
    temp(this, null);

为什么会这样?第二个版本如何成为线程安全的?最佳实践是什么?

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

是一篇关于.NET事件和线程竞争条件的好文章。它涵盖了一些常见的场景,并提供了一些很好的参考


希望这能有所帮助。

事件在语法上确实比一系列代表更重要。当您调用事件时,这实际上是在该列表上迭代,并使用您传递的参数调用每个委托

线程的问题在于,它们可能通过订阅/取消订阅来添加或删除此集合中的项目。如果他们在您迭代集合时这样做,这将导致问题(我认为会引发异常)

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


注意:然而,现在即使在您取消订阅之后,您的侦听器也可以被调用,所以您应该确保在侦听器代码中处理这个问题

IMO,其他答案遗漏了一个关键细节,即委托(以及事件)是不可变的。这一点的意义在于,订阅或取消订阅事件处理程序并不是简单地附加/删除到列表中,而是用一个新列表替换该列表,其中包含一个额外(或更少)项

由于引用是原子的,这意味着在执行以下操作时:

var handler = SomeEvent;
您现在有一个无法更改的刚性实例,即使在下一皮秒内另一个线程取消订阅(导致实际事件字段变为
null


因此,您测试并调用null,一切正常。当然,请注意,仍然存在一个令人困惑的场景,即在一个认为它在皮秒前已取消订阅的对象上引发事件

为什么不能在第二条语句中发生呢?读取的是
SomeEvent
是原子的,也就是说,它要么一直发生,要么什么也没有发生。因此,
temp
不能在该线程之外修改,因为它是本地线程。实际上,当您迭代集合时,它不会引起问题。这里的代表是不变的。唯一的问题是在检查是否有人订阅并实际调用事件处理程序之前的一秒钟。一旦您开始执行这些,背景更改将不会影响当前调用。添加到Lasse V.Karlsen的注释中,您可以通过使用SomeEvent?.Invoke(…)来消除最后一个竞争条件。调用(…)而不是在调用之前检查null。阅读,这里的限定答案我感觉到C#中的事件处理是紧密耦合的,容易出错,而且理解得不好。这解决了一个问题,但是替换本身是否安全地以这样一种方式进行,以保证所有请求的操作都会发生?也就是说,如果两个线程试图同时订阅不同的委托,是否保证最终会同时订阅这两个委托,或者其中一个订阅可能会自动失败?@binki-yes;它使用互锁的exchange循环(现代编译器)或同步(
)区域(旧编译器)