事件处理程序不是线程安全的?(复制)

时间:2022-05-29 23:59:59

This question already has an answer here:

这个问题已经有了答案:

So i've read around that instead of calling a event directly with

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

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

i should be doing

我应该做的

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

Why is this so? How does the second version become thread safe? What is the best practice?

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

4 个解决方案

#1


14  

Events are really syntactic sugar over a list of delegates. When you invoke the event, this is really iterating over that list and invoking each delegate with the parameters you have passed.

事件实际上是代表列表的语法糖。当您调用该事件时,它实际上是遍历该列表,并使用您传递的参数调用每个委托。

The problem with threads is that they could be adding or removing items from this collection by subscribing/unsubscribing. If they do this while you are iterating the collection this will cause problems (I think an exception is thrown)

线程的问题是,它们可以通过订阅/取消订阅从这个集合中添加或删除项。如果在迭代集合时它们这样做,将导致问题(我认为抛出了一个异常)

The intent is to copy the list before iterating it, so you are protected against changes to the list.

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

Note: It is however now possible for your listener to be invoked even after you unsubscribed, so you should make sure you handle this in your listener code.

注意:现在您的侦听器甚至在未订阅之后也可以被调用,所以您应该确保在侦听器代码中处理这个问题。

#2


30  

IMO, the other answers miss one key detail - that delegates (and therefore events) are immutable. The significance of this is that subscribing or unsubscribing an event handler doesn't simply append/remove to a list - rather, it replaces the list with a new one with an extra (or one less) item on it.

在我看来,其他的答案漏掉了一个关键细节——委托(以及事件)是不可变的。这一点的意义在于,订阅或取消订阅事件处理程序并不简单地向列表添加/删除——相反,它用一个新列表替换一个新列表,其中包含一个额外的(或更少的)项。

Since references are atomic, this means that at the point you do:

由于引用是原子性的,这就意味着您要做的是:

var handler = SomeEvent;

you now have a rigid instance that cannot change, even if in the next picosecond another thread unsubscribes (causing the actual event field to become null).

现在您有了一个不能更改的严格实例,即使在下一个皮秒中另一个线程取消订阅(导致实际事件字段变为null)。

So you test for null and invoke it, and all is well. Note of course that there is still the confusing scenario of the event being raised on an object that thinks it unsubscribed a picosecond ago!

你测试null并调用它,一切都很好。当然,仍然存在一个令人困惑的场景,即在一个认为它在一皮秒前就没有订阅的对象上引发事件!

#3


5  

Best practice is the second form. The reason is that another thread might null or alter SomeEvent between the 'if' test and the invocation.

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

#4


2  

Here is a good write up about .NET events and race conditions with threads. It covers some common scenarios and has some good references in it.

这里有一篇关于。net事件和线程竞争条件的文章。它涵盖了一些常见的场景,并在其中提供了一些很好的参考。

Hope this helps.

希望这个有帮助。

#1


14  

Events are really syntactic sugar over a list of delegates. When you invoke the event, this is really iterating over that list and invoking each delegate with the parameters you have passed.

事件实际上是代表列表的语法糖。当您调用该事件时,它实际上是遍历该列表,并使用您传递的参数调用每个委托。

The problem with threads is that they could be adding or removing items from this collection by subscribing/unsubscribing. If they do this while you are iterating the collection this will cause problems (I think an exception is thrown)

线程的问题是,它们可以通过订阅/取消订阅从这个集合中添加或删除项。如果在迭代集合时它们这样做,将导致问题(我认为抛出了一个异常)

The intent is to copy the list before iterating it, so you are protected against changes to the list.

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

Note: It is however now possible for your listener to be invoked even after you unsubscribed, so you should make sure you handle this in your listener code.

注意:现在您的侦听器甚至在未订阅之后也可以被调用,所以您应该确保在侦听器代码中处理这个问题。

#2


30  

IMO, the other answers miss one key detail - that delegates (and therefore events) are immutable. The significance of this is that subscribing or unsubscribing an event handler doesn't simply append/remove to a list - rather, it replaces the list with a new one with an extra (or one less) item on it.

在我看来,其他的答案漏掉了一个关键细节——委托(以及事件)是不可变的。这一点的意义在于,订阅或取消订阅事件处理程序并不简单地向列表添加/删除——相反,它用一个新列表替换一个新列表,其中包含一个额外的(或更少的)项。

Since references are atomic, this means that at the point you do:

由于引用是原子性的,这就意味着您要做的是:

var handler = SomeEvent;

you now have a rigid instance that cannot change, even if in the next picosecond another thread unsubscribes (causing the actual event field to become null).

现在您有了一个不能更改的严格实例,即使在下一个皮秒中另一个线程取消订阅(导致实际事件字段变为null)。

So you test for null and invoke it, and all is well. Note of course that there is still the confusing scenario of the event being raised on an object that thinks it unsubscribed a picosecond ago!

你测试null并调用它,一切都很好。当然,仍然存在一个令人困惑的场景,即在一个认为它在一皮秒前就没有订阅的对象上引发事件!

#3


5  

Best practice is the second form. The reason is that another thread might null or alter SomeEvent between the 'if' test and the invocation.

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

#4


2  

Here is a good write up about .NET events and race conditions with threads. It covers some common scenarios and has some good references in it.

这里有一篇关于。net事件和线程竞争条件的文章。它涵盖了一些常见的场景,并在其中提供了一些很好的参考。

Hope this helps.

希望这个有帮助。