EventHandler <teventargs> C#中的线程安全性?</teventargs>

时间:2012-07-16 13:08:13

标签: c# .net events .net-4.0

使用我的cusom EventArgs,例如:

public event EventHandler<MyEventArgs> SampleEvent;

来自msdn例如:

public class HasEvent
{
// Declare an event of delegate type EventHandler of 
// MyEventArgs.

    public event EventHandler<MyEventArgs> SampleEvent;

    public void DemoEvent(string val)
    {
    // Copy to a temporary variable to be thread-safe.
        EventHandler<MyEventArgs> temp = SampleEvent;
        if (temp != null)
            temp(this, new MyEventArgs(val));
    }
}

我有 2 问题:

1) 看着标记的代码:

enter image description here

我没有看到为什么应该将它复制到另一个参数(关于线程)的原因

因为我们有event keyowrd,所以没有人可以触及它的调用列表(我的意思是没有局外代码)

2)如果我没有弄错,DemoEvent函数应该是虚拟的,所以它可以在子类中被覆盖......(我确定我已经在某处看到了它)

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

所以如果我有这个代码:

enter image description here

它建议我:

enter image description here

当我按下它时:

enter image description here

再次提出 2 问题:

1)关于线程安全,这条线EventHandler<MyEventArgs> temp = SampleEvent;将解决的场景是什么?

2)该功能不应该是virtual吗? (我确定我已经看到虚拟的这种模式)

2 个答案:

答案 0 :(得分:10)

  

这一行的事件是什么情况EventHandler temp = SampleEvent;关于线程安全会解决吗?

想象一下你这样做:

if (SampleEvent != null)
    SampleEvent(this, new MyEventArgs());

如果另一个线程将在if之后但在调用之前分离事件处理程序,那么你将尝试调用null委托(并且你将获得异常)。

  

该功能不应该是虚拟的吗? (我确定我已经看到虚拟的这种模式)

是的,如果该课程不是sealed,那么您应该标记该职能virtual(这不是强制性的,但这是一个被广泛接受的模式)。

修改

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

在这种情况下,在时间4,您将调用null委托,它将抛出NullReferenceException。现在看看这段代码:

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      }

现在在时间5,您拨打sampleEvent并保留SampleEvent内容,在这种情况下,它不会抛出任何异常(但它会调用MyHandler即使它被第二个帖子删除了。)

答案 1 :(得分:5)

 if (SampleEvent != null)
    SampleEvent(this, new MyEventArgs(val));

这是一场经典的穿线比赛。在此代码运行时,另一个线程可以取消订阅事件处理程序。这使得if()语句断定存在订阅者,但事件调用因NullReferenceException而失败。将委托对象复制到本地变量可确保通过取消订阅事件处理程序来更改委托对象引用的客户端代码不会导致崩溃。还有一个问题,你会在取消订阅之后调用事件处理程序,但这是一个不可避免的竞争,并不一定像NRE那样致命,并且可以由事件处理程序处理,与NRE不同。

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