【问题标题】:How do I fire an event safely如何安全地触发事件
【发布时间】:2009-12-08 18:21:26
【问题描述】:

当事件没有订阅者时,如何确保在触发事件时不会引发异常。

 // Delegate declaration
 public delegate void _delDisplayChange(object sender,string option);

 // Event declaration
 public event _delDisplayChange  DisplayChange;

 //throwing the event
 DisplayChange(this, "DISTRIBUTION");

【问题讨论】:

标签: c# events .net-2.0


【解决方案1】:

这是推荐的方法:

protected void RaiseDisplayChanged(string message)
{
    var handlers = DisplayChange;
    if(handlers != null)
        handlers(this, message);
}

在检查之前复制事件处理程序枚举有两件事:

  1. 如果 DisplayChange 处理程序在检查和触发之间变为 null,您不会死
  2. 如果侦听器在枚举时修改 DisplayChange 列表,您不会遇到奇怪的事情。

另外,您没有使用标准事件协议。您的代表应该是:

public delegate void DisplayChangeDelegate(object sender, OptionsEventArgs args);

其中 OptionsEventArgs 派生自 EventArgs。更进一步,在 .Net 3.5 中,您永远不应该像这样定义委托。相反,您应该只定义您的事件:

public event EventHandler<OptionsEventArgs> DisplayChanged;

我想通过定义这个类更进一步:

public class EventArgs<T> : EventArgs
{
    public T Payload { get; private set }
    public EventArgs(T payload)
    {
        Payload = payload;
    }
}

那么,你就不需要定义 OptionsEventArgs:

public event EventHandler<EventArgs<string>> DisplayChanged;

只是一些需要考虑的事情......

【讨论】:

  • 很好的答案,但也值得解释为什么支持字段可以变成null(多线程),以及为什么值得一开始就进行事件注册和提高线程安全。
  • 应该考虑将RaiseDisplayChanged(string)设为虚拟,这样派生类可以覆盖事件的内部行为。
【解决方案2】:

改变这个:

// Event declaration      
public event _delDisplayChange  DisplayChange;

到这里:

// Event declaration      
public event _delDisplayChange  DisplayChange = delegate{};

这将确保您的活动始终拥有至少一名订阅者。

【讨论】:

  • 无需添加占用内存且不遵守任何逻辑的虚构订阅者;检查 null 更符合事件/订阅者逻辑的基本原理。
  • 这是一个有趣的转折点,但正如 Cesar 所说,还有更好的方法。
  • 什么是“事件/订阅者逻辑的基本原理”,为什么检查 null 是一致的?
  • 事件/订阅者逻辑的基本原理是订阅者订阅事件,没有订阅者的事件有一个空订阅者列表,因此如果被调用就会失败(无论如何,.NET 就是这样实现它的)。在触发事件之前检查订阅者列表是否为空与此一致。添加虚假订阅者只是为了避免进行检查是一种快速而肮脏的技巧,它在短期内有效,但与事件/订阅者的底层语义不一致。这类似于在循环中更改循环变量的值:它可以工作,但不能。
  • +1 添加一个空订阅者不是一个肮脏的把戏。这本质上是 null 对象模式:您将 null 的特殊处理替换为具有相同效果的对象(在本例中为委托)。空对象模式是更面向对象的方法。 en.wikipedia.org/wiki/Null_Object_pattern
【解决方案3】:

正如布赖恩所说:许多消息来源建议在检查事件是否为空之前制作一个副本:

_delDisplayChange displayChangeCopy = DisplayChange;
if (displayChangeCopy != null)
    displayChangeCopy(this, "DISTRIBUTION");

这有助于使您的代码更加线程安全,因为 displayChangeCopy 的值在 null 检查和调用之间不会改变。

【讨论】:

  • 这不会使您的代码线程安全。它消除了一个竞争条件。它并没有消除更一般的竞争条件,即在 DisplayChange 的值的存储和本地副本的调用之间,DisplayChange 的内容可能已经改变,因此 你是现在调用一个可能依赖于已销毁状态的函数。不要在没有明确描述您正在减轻的线程危险的情况下声称代码是“线程安全的”。您几乎没有减轻这里的所有危险。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-27
  • 1970-01-01
  • 1970-01-01
  • 2023-03-15
  • 2023-02-14
  • 1970-01-01
相关资源
最近更新 更多