Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/spring/11.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在多线程环境中引发事件_C#_.net_Multithreading_Concurrency_Memory Model - Fatal编程技术网

C# 在多线程环境中引发事件

C# 在多线程环境中引发事件,c#,.net,multithreading,concurrency,memory-model,C#,.net,Multithreading,Concurrency,Memory Model,自.NET 4.0以来,自动生成的添加/删除事件处理程序是线程安全的(和)。因此,将侦听器注册到公开事件的客户机可以从多个线程同时注册,而无需进行争用 但是,如果我想以线程安全的方式触发事件,该怎么办?建议的做法如下(): 然而,在阅读了一些关于.NET内存模型的内容(例如MSDN杂志和)后,我不再认为这是正确的。我担心的是,编译器可能会引入内存读取,因此上面的代码可能会被JIT转换为如下内容: public event EventHandler MyEvent; protected void

自.NET 4.0以来,自动生成的添加/删除事件处理程序是线程安全的(和)。因此,将侦听器注册到公开事件的客户机可以从多个线程同时注册,而无需进行争用

但是,如果我想以线程安全的方式触发事件,该怎么办?建议的做法如下():

然而,在阅读了一些关于.NET内存模型的内容(例如MSDN杂志和)后,我不再认为这是正确的。我担心的是,编译器可能会引入内存读取,因此上面的代码可能会被JIT转换为如下内容:

public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
    // JIT removed the local variable and introduced two memory reads instead.
    if (MyEvent != null)
    {
        // A race condition may cause the following line to throw a NullReferenceException.
        MyEvent(this, e);
    }
}
删除局部变量并使用重复的内存读取是合法的,因为如果在单线程环境中执行,它不会改变方法的行为。这是由ECMA规范()规定的。MSDN杂志也提供了可理解的例子


我是不是遗漏了什么?如果没有,请建议解决方法。

您必须添加唯一一行,以便在多线程环境中更正第一个代码段:

public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
    EventHandler myEvent = MyEvent;
    Thread.MemoryBarrier();
    if (myEvent != null)
    {
        myEvent(this, e);
    }
}

内存屏障拒绝对编译器和CPU的读写进行重新排序。这就是易失性读/写的实现方式。你可以读更多

谢谢你指出这一点。我在网上搜索了这么多,但我显然错过了那个帖子。再加上我的两分钱,我实际上认为你所指的问题选择了一个错误的答案。正确的答案似乎是-。ECMA规范确实允许用重复的内存读取来替换局部变量。添加了我自己问题的两个副本——尤其是Eric Lippert的一个很好的答案。我的理解是,内存屏障是一道屏障,它禁止内存读写的重新排序(例如,通过编译器或处理器缓存)。然而,这并不意味着记忆障碍也会影响记忆读取的方式。请给我提供支持您观点的参考资料。编译器和处理器不向我们提供任何担保,也不保证此代码将产生多少物理写入和读取。它甚至可能因电话而异。是的,记忆障碍并不影响这种行为。这意味着,在没有MB引用的情况下,可以从任何位置获取事件委托:内存、缓存、寄存器。内存和缓存由多个线程修改。这就是为什么NRE是可能的。内存障碍迫使编译器在线程堆栈中存储对委托的引用。其他线程无法修改它。这使检查和调用变得安全。感谢Jury对我的支持,但是将值从堆移动到线程堆栈对我来说没有意义-然后值如何返回到堆?此外,我相信ECMA标准并未提及任何此类操作。
public event EventHandler MyEvent;
protected void OnMyEvent(EventArgs e)
{
    EventHandler myEvent = MyEvent;
    Thread.MemoryBarrier();
    if (myEvent != null)
    {
        myEvent(this, e);
    }
}