为什么临时变量会阻止客户端删除事件处理程序?

时间:2009-05-07 15:16:08

标签: c# multithreading events

以下代码段来自Effective C#,

public event AddMessageEventHandler Log;

public void AddMsg ( int priority, string msg )

{
    // This idiom discussed below.
    AddMessageEventHandler l = Log;
    if ( l != null )
        l ( null, new LoggerEventArgs( priority, msg ) );
}

AddMsg方法显示了引发事件的正确方法。 引用日志事件处理程序的临时变量是防止竞争条件的重要保护措施 多线程程序。如果没有引用的副本,客户端可以删除if语句检查和事件处理程序执行之间的事件处理程序。通过 复制参考,这是不可能发生的。

为什么临时变量会阻止客户端删除事件处理程序?我必须在这里遗漏一些东西。

6 个答案:

答案 0 :(得分:3)

它不会阻止客户端删除事件处理程序 - 它只是意味着你仍然会调用该事件处理程序。

您可能缺少的重要一点是委托是不可变的 - 当删除事件处理程序时,Log的值将更改为新委托或null。这没关系,因为在那个阶段你使用的是1而不是Log

答案 1 :(得分:2)

委托链是不可变的。因此,如果另一个线程访问“Log”并删除了一个事件处理程序,则会为Log分配一个新的委托链。因此,当访问l时,即使从Log中删除了事件处理程序,它也不会影响l,因为它将不再“指向”同一个委托链。所以是的,它确实可以防止竞争条件,但是你最终会遇到一个线程取消订阅的情况,但仍会调用evanthandler。

答案 2 :(得分:2)

它不会阻止客户端删除事件处理程序 - 它只是意味着如果它们不会最终调用空委托...考虑:

  • 主题A:检查Log是否为空
  • 主题B:取消订阅,导致Log变为空
  • 线程A:调用Log(现在为null)= boom

当然,通过上述修复,您现在可以进行幻像调用 - 即订阅者可以在取消订阅之后调用事件...得到爱线程。

一如既往,Eric Lippert有一个关于这个主题的博客:Events and Races

答案 3 :(得分:1)

客户端仍然可以删除事件处理程序。

为:

if ( Log != null )
{
  //another thread removes the event handler at this point,
  // Log is now null
  Log ( null, new LoggerEventArgs( priority, msg ) );
}

好:

AddMessageEventHandler l = Log; 
if ( l != null )
  //another thread removes the event handler at this point,
  // Log is now null
  // l is not null, so we are safe.
l ( null, new LoggerEventArgs( priority, msg ) );

答案 4 :(得分:0)

因为当你指定它时,你创建了一个对象的本地引用,然后在继续之前检查引用是否存在。

如果您刚刚检查了Log是否为null,则继续执行下一个语句,另一个线程可能会使2个指令之间的Log对象为空。

If(Log != null)
// another thread could null the reference to Log here
l.DoSomething()

AddMessageEventHandler l = Log; 
if ( l != null )
// nothing can null the reference here as you’ve created a local copy of that reference
l ( null, new LoggerEventArgs( priority, msg ) );
}

答案 5 :(得分:0)

你加粗的文字解释了什么可能出错。

  

如果没有引用的副本,客户端可以删除if语句检查和事件处理程序执行之间的事件处理程序。

在AddMsg方法运行时,第二个线程可能会从Log中删除其事件处理程序。例如:

if (Log != null)
{
    // other thread has just removed its event handler, so Log is now null

    Log(null, new LoggerEventArgs(priority, msg)); // Oops! Throws NullReferenceException
}