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

Roy*_*mir 13 .net c# events .net-4.0

使用我的cusom EventArgs,例如:

public event EventHandler<MyEventArgs> SampleEvent;
Run Code Online (Sandbox Code Playgroud)

来自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));
    }
}
Run Code Online (Sandbox Code Playgroud)

我有两个问题:

1)查看标记的代码:

在此输入图像描述

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

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

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

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

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

在此输入图像描述

它暗示我:

在此输入图像描述

当我按下它时:

在此输入图像描述

所以我的两个问题:

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

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

Adr*_*tti 10

这行EventHandler temp = SampleEvent是什么情形; 关于线程安全会解决吗?

想象一下你这样做:

if (SampleEvent != null)
    SampleEvent(this, new MyEventArgs());
Run Code Online (Sandbox Code Playgroud)

如果另一个线程将在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即使它被第二个线程删除它也会调用).

  • @apokryfos是的,这是真的.更好的解决方案应该涉及自定义事件实现,但即使在这种情况下,您可能会遇到一些线程和锁的不良行为).没有任何(AFAIK)良好和通用的解决方案,可能如果您必须避免这些问题,您必须为事件实现和调用编写长/慢自定义代码. (2认同)

Han*_*ant 5

 if (SampleEvent != null)
    SampleEvent(this, new MyEventArgs(val));
Run Code Online (Sandbox Code Playgroud)

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

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

  • 委托对象是不可变的.这意味着您无法更改调用列表.您只能使用修改后的列表创建*new*委托对象.这是底层Delegate.Combine()的作用,它返回一个新对象.就像字符串一样,您也无法修改其内容.您只能创建一个新字符串. (2认同)