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

C# 为什么临时变量可以阻止客户端删除事件处理程序?,c#,multithreading,events,C#,Multithreading,Events,下面的代码片段来自《有效的C#》 AddMsg方法显示了引发事件的正确方法引用日志事件处理程序的临时变量是一个重要的保护措施,可以防止在 多线程程序。如果没有引用的副本,客户端可以删除if语句检查和事件处理程序执行之间的事件处理程序。通过 复制引用,这不会发生。 为什么临时变量可以阻止客户端删除事件处理程序?我一定是遗漏了什么。委托链是不可变的。因此,如果另一个线程访问“日志”并删除eventhandler,则会为日志分配一个新的委托链。因此,当访问l时,即使从日志中删除eventhandler

下面的代码片段来自《有效的C#》

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


为什么临时变量可以阻止客户端删除事件处理程序?我一定是遗漏了什么。

委托链是不可变的。因此,如果另一个线程访问“日志”并删除eventhandler,则会为日志分配一个新的委托链。因此,当访问l时,即使从日志中删除eventhandler,它也不会影响l,因为它不再“指向”同一委托链。因此,是的,它确实可以防止竞争条件,但是您可能会遇到一个场景,其中一个线程取消订阅,但Evanshandler仍将被调用。

委托链是不可变的。因此,如果另一个线程访问“日志”并删除eventhandler,则会为日志分配一个新的委托链。因此,当访问l时,即使从日志中删除eventhandler,它也不会影响l,因为它不再“指向”同一委托链。因此,是的,它确实可以防止竞争条件,但是您可能会遇到这样的情况:一个线程取消订阅,但仍然会调用Evanshandler。

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

坏的:

好:


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

坏的:

好:


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

如果您只是检查Log是否不为null,然后继续执行下一条语句,那么另一个线程可能会在两条指令之间使Log对象为null

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 ) );
}

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

如果您只是检查Log是否不为null,然后继续执行下一条语句,那么另一个线程可能会在两条指令之间使Log对象为null

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 ) );
}

它不会阻止客户端删除事件处理程序——它只是意味着您无论如何都要调用该事件处理程序


您可能缺少的重要一点是委托是不可变的-当删除事件处理程序时,
Log
的值将更改为新委托或
null
。不过,这没关系,因为在这个阶段,您使用的是
1
而不是
Log
,它不会阻止客户端删除事件处理程序,这只是意味着您无论如何都会调用该事件处理程序


您可能缺少的重要一点是委托是不可变的-当删除事件处理程序时,
Log
的值将更改为新委托或
null
。不过这没关系,因为在这个阶段,您使用的是
1
而不是
Log
,它不会阻止客户端删除事件处理程序-这只是意味着,如果客户端删除了事件处理程序,您就不会调用空委托。。。考虑:

  • 线程A:检查
    Log
    是否为空
  • 线程B:取消订阅,导致
    Log
    变为空
  • 线程A:调用
    Log
    (现在为空)=boom
当然,使用上面的修复程序,您现在有了一个幻象调用-即订阅者可以在取消订阅后调用事件。。。我喜欢穿线


像往常一样,Eric Lippert有一个关于这个主题的博客:

它不会阻止客户端删除事件处理程序-它只是意味着如果他们这样做了,你就不会调用空委托。。。考虑:

  • 线程A:检查
    Log
    是否为空
  • 线程B:取消订阅,导致
    Log
    变为空
  • 线程A:调用
    Log
    (现在为空)=boom
当然,使用上面的修复程序,您现在有了一个幻象调用-即订阅者可以在取消订阅后调用事件。。。我喜欢穿线


和往常一样,埃里克·利珀特(Eric Lippert)也有一个关于这一主题的博客:

你加粗的文字解释了可能出现的问题

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

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

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
}

粗体显示的文本解释了可能出现的问题

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

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

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
}
另见:另见:
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
}