繁体   English   中英

事件处理程序 <TEventArgs> C#中的线程安全?

[英]EventHandler<TEventArgs> thread safety in C#?

使用我的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));
    }
}

我有两个问题:

1)查看标记的代码:

在此输入图像描述

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

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

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

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

所以,如果我有这个代码:

在此输入图像描述

它暗示我:

在此输入图像描述

当我按下它时:

在此输入图像描述

所以我的两个问题:

1)此行EventHandler<MyEventArgs> temp = SampleEvent;的情况是什么? 关于线程安全会解决吗?

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

这行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 ,即使它已被第二个线程删除)。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM