EventHandler<;TEventArgs>;C#中的线程安全性?

EventHandler<;TEventArgs>;C#中的线程安全性?,c#,.net,events,.net-4.0,C#,.net,Events,.net 4.0,使用我的cusom事件参数,例如: public event EventHandler<MyEventArgs> SampleEvent; public event事件处理程序SampleEvent; 例如: 公共类事件 { //声明委托类型为的EventHandler的事件 //MyEventArgs。 公共事件事件处理程序SampleEvent; 公共void DemoEvent(字符串val) { //复制到临时变量以确保线程安全。 EventHandler temp=Sa

使用我的cusom事件参数,例如:

public event EventHandler<MyEventArgs> SampleEvent;
public event事件处理程序SampleEvent;
例如:

公共类事件
{
//声明委托类型为的EventHandler的事件
//MyEventArgs。
公共事件事件处理程序SampleEvent;
公共void DemoEvent(字符串val)
{
//复制到临时变量以确保线程安全。
EventHandler temp=SampleEvent;
如果(温度!=null)
临时(这是新的MyEventArgs(val));
}
}
我有两个问题:

(一) 查看标记的代码:

我看不出为什么要将它复制到另一个参数(关于线程)

因为我们有
事件
keyowrd,所以没有人可以触摸它的调用列表(我的意思是没有类的外部代码)

2) 如果我没有弄错的话,
DemoEvent
函数应该是虚拟的,这样就可以在子类中重写它。。。(我肯定在什么地方见过)

奇怪的是,resharper也不会添加虚拟:

如果我有这个代码:

Time Thread 1 Thread 2 1 obj.SampleEvent += MyHandler; 2 var sampleEvent = SampleEvent; 3 if (sampleEvent != null) 4 { obj.SampleEvent -= MyHandler; 5 sampleEvent(this, new MyEventArgs()); 6 }

它让我想到:

当我按下它时:

那么我的2个问题:

1) 这一行
EventHandler temp=SampleEvent的场景是什么将解决线程安全问题

2) 功能不应该是虚拟的吗?(我肯定我在虚拟机上见过这种模式)

这行EventHandler temp=SampleEvent的场景是什么;要解决,关于线程安全

想象你这样做:

if (SampleEvent != null)
    SampleEvent(this, new MyEventArgs());
如果另一个线程将在If之后但在调用之前分离事件处理程序,那么您将尝试调用
null
委托(您将得到一个异常)

功能不应该是虚拟的吗?(我确信我在虚拟机上看到了这种模式)

是的,如果类不是密封的
,那么您应该将该函数标记为虚拟的(这不是强制性的,但它是一种公认的模式)

编辑

Time Thread 1 Thread 2 1 obj.SampleEvent += MyHandler; 2 if (SampleEvent != null) 3 { obj.SampleEvent -= MyHandler; 4 SampleEvent(this, new MyEventArgs()); 5 } 现在,在时间5,您调用
sampleEvent
,它保存
sampleEvent
旧内容,在这种情况下,它不会抛出任何异常(但它将调用
MyHandler
,即使它已被第二个线程删除)

这是一场经典的线程竞赛。当此代码运行时,另一个线程可以取消订阅事件处理程序。这使得if()语句得出结论,存在订阅者,但事件调用失败,出现NullReferenceException。将委托对象复制到局部变量可确保客户端代码通过取消订阅事件处理程序更改委托对象引用不会导致崩溃。仍然是一个问题,您将在取消订阅后调用事件处理程序,但这是不可避免的竞争,不一定像NRE那样致命,并且可以由事件处理程序处理,不像NRE


是的,像这样的方法通常被设置为受保护的虚拟方法,并命名为OnSampleEvent(),因此派生类可以更改事件引发行为。

我想这意味着某些订阅者即使在分离后也可能会触发事件。@apokryfos是的,这是真的。更好的解决方案应该包括自定义事件实现,但即使在这种情况下,线程和锁也可能会出现一些不需要的行为)。没有任何(AFAIK)好的通用解决方案,如果您必须避免这些问题,您可能必须为事件实现和调用编写一个长/慢的自定义代码。如果另一个线程将在if _…ok…之后分离事件处理程序。。。。那么,如果在_ifstatemnt之后,另一个线程删除了调用列表,那么它是如何实现线程安全的呢?@RoyiNamir这是线程安全的,意义非常轻松。它不会使应用程序崩溃(因为您不会调用空委托),但仅此而已(正如apokryfos所指出的,即使在您分离处理程序之后,您也可能会被调用)。Resharper只是对与错的一个观点。这不是一个绝对的注意,实际上有两个种族条件在这里。此代码更改仅修复其中一个种族。请参阅Eric Lippert的优秀文章,以获得完整的解释。复制到局部变量的可能重复:我不明白,委托是引用类型,因此如果另一个变量等于此引用类型,则它们指向相同的内存位置。那么你的意思是什么?委托对象是不可变的,就像字符串一样。添加事件处理程序将创建一个新对象。temp变量引用了旧变量。@HansPassant,看看,我标记了在多线程环境中可能导致问题的点。如果线程进入第45行,它可以删除调用列表(那么这里的保护在哪里?),同样,在第47行(中间),线程仍然可以删除调用列表。如果你能回答委托对象是不可变的,我会很高兴的。这意味着您不能更改调用列表。只能使用修改过的列表创建新的委托对象。下面的Delegate.Combine()就是这样做的,它返回一个新对象。就像字符串一样,您也不能修改其内容。您只能创建一个新字符串。 Time Thread 1 Thread 2 1 obj.SampleEvent += MyHandler; 2 var sampleEvent = SampleEvent; 3 if (sampleEvent != null) 4 { obj.SampleEvent -= MyHandler; 5 sampleEvent(this, new MyEventArgs()); 6 }
 if (SampleEvent != null)
    SampleEvent(this, new MyEventArgs(val));