【问题标题】:Raising events in C# that ignore exceptions raised by handlers在 C# 中引发忽略处理程序引发的异常的事件
【发布时间】:2010-06-24 20:59:03
【问题描述】:

我对在 C# 中引发事件的一个最讨厌的事实是,事件处理程序中的异常会破坏我的代码,并且可能会阻止调用其他处理程序,如果破坏的处理程序碰巧首先被调用的话;在大多数情况下,我的代码不会在意监听其事件的其他人的代码是否损坏。

我创建了一个捕获异常的扩展方法:

public static void Raise(this EventHandler eh, object sender, EventArgs e)
{
  if (eh == null)
    return;
  try
  {
    eh(sender, e);
  }
  catch { }
}

尽管这确实意味着我的代码无论如何都会继续运行,但此方法不会阻止第一个事件处理程序抛出异常并阻止第二个和后续处理程序收到事件通知。我正在研究一种通过 GetInvocationList 进行迭代的方法,以将每个单独的事件处理程序包装在它自己的 try/catch 中,但这似乎效率低下,而且我不确定最好的方法,或者即使我应该这样做。

另外,我真的很不习惯在这里忽略异常(FxCop/Resharper 也不是);实际上,在这种情况下,异常应该发生什么?

【问题讨论】:

  • 我经常想知道处理这个问题的最佳方法是什么。
  • 这实际上提出了另一个问题,我将单独提出;在这个问题中,我从引发事件的类的角度进行编码,但我想知道是否应该首先允许事件处理程序抛出异常。 stackoverflow.com/questions/3114543/…

标签: c# events exception


【解决方案1】:

如果你做了这样的事情呢?

public static void Raise(this EventHandler eh, object sender, EventArgs e)
{
    if (eh == null)
        return;

    foreach(var handler in eh.GetInvocationList().Cast<EventHandler>())
    {
        try
        {
            handler(sender, e);
        }
        catch { }
    }
}

【讨论】:

  • 实际上,我做了foreach (Delegate handler in WeaponChanged.GetInvocationList()) handler(sender, e);,但不幸的是,handler(sender, e) 在 VS 中给出了一个错误,即 'handler' 的 'Method, delegate or event is expected';我对那个摸不着头脑,这显然是一个代表。
  • @Flynn1179 - 在这件事上不能自行调用委托。您要么需要像在我的示例中看到的那样强制转换,要么像这样调用委托:handler.DynamicInvoke(sender, e);
  • aah.. 我对“代表”和“代表”感到困惑:)
  • 好的,哪个更快 - 投射然后执行handler(sender, e),还是不投射并执行handler.DynamicInvoke(sender, e)?我知道我可以通过快速测试来做到这一点,但我很好奇为什么一个更快,或者更好
  • @Flynn1179 - 顾名思义,调用DynamicInvoke 可以接受任何参数,但在运行时这些参数将被验证。将代表转换为正确的类型将删除检查,实际上会提高性能。实际数字很难衡量,因为这取决于您拥有多少订阅者以及该事件的发起频率。
【解决方案2】:

您应该只捕获那些您可以处理的异常。例如,您可能正在访问一个文件并且没有足够的权限。如果您知道这种情况偶尔会发生,并且只需要记录该案例,然后捕获该异常

如果您的代码无法处理异常,请不要捕获它。

这是一种特殊情况,因此其他事件处理程序很有可能也无法“做他们的事情”,所以让它传播到可以安全处理的水平。

【讨论】:

    【解决方案3】:

    如果事件处理程序没有捕获异常,我看不出有什么好的方法可以在抛出事件的类中处理异常。您没有任何上下文可以知道处理程序在做什么。在这种情况下,让应用程序严重崩溃是最安全的,因为您不知道其他事件处理程序可能造成的可能会破坏您的应用程序稳定性的副作用。这里的教训是事件处理程序必须是健壮的(处理它们自己的异常),因为触发事件的类不应该捕获处理程序抛出的异常。

    【讨论】:

    • 在调试时快速失败是件好事,但对于用户而言则不然。为什么一个有缺陷的插件会导致它接触到的每个应用程序都崩溃?
    • 如果你有一个有缺陷的插件并且你想要一个真正健壮的架构,那么你需要隔离插件,这样它就不会损害你的应用程序。您可以使用 AppDomain 或单独的进程来执行此操作。如果它不是孤立的,那么你就不能保证你的状态没有被破坏,并且尽管有一个未处理的异常继续运行,这会导致更糟糕的失败。基本上,运行不正确比根本不运行更糟糕,如果你不能确定你仍然可以正确运行,那最好失败。
    • @Gabe - 一个有问题的插件应该会导致它接触到的每个应用程序崩溃,因为它是一个有问题的插件。应该让人们意识到它是错误的,因此可以修复或避免它。以牺牲用户数据为代价来保护糟糕的插件是一个糟糕的策略。
    • @Dan - 没有什么是一成不变的,他们可能必须这样做,以便服务或工作可以继续处理。
    • @Chaos,我以前也遇到过这种情况,客户看到“崩溃”的感知成本如此之高,以至于不惜一切代价保持应用程序运行的压力。随着我对让未知故障滑落可能导致的各种丑陋后果积累更多经验,我对这种想法的抵抗力越来越强。
    【解决方案4】:

    一个简单但重要的规则:

    代码中的每个缺陷都应该是 尽早致命 可能。

    通过这种方式,错误被发现并修复,软件变得越来越强大。别人说的是对的;永远不要捕捉到你没有预料到并且知道如何处理的异常。

    【讨论】:

    • 每个人都这么说,但事情并不总是那么简单。如果他们无法控制谁订阅了该事件怎么办?如果必须继续处理怎么办?
    • @Chaos,在这种情况下,您需要以允许隔离的方式构建事件。例如,设置具有隔离进程的客户端/服务器模型。现在客户端可以崩溃,服务器可以愉快地继续处理,即使客户端有错误。如果您有没有隔离边界的简单回调事件,则无法防止不守规矩的客户端干扰您的处理。进程不能保证它的必要性(继续正确处理),进程假装它可以是不负责任的。
    • Dan:假设这都是托管代码,你会如何期望一个有缺陷的异常处理程序干扰主进程?
    • @Gabe,我假设您的意思是事件处理程序。问题是它可能存在于可以访问共享上下文的类上。例如,它可能正在使用数据库,或者它可能正在向某些机器发送命令。如果发生异常,则可能已经造成了一些损害,但如果您忽略未知异常,您只需让损害复合。也就是说,您可以通过良好地使用防御性编码实践来最大限度地减少损坏的可能性。更好的是,在服务层处理已知异常,它们不会出现在事件处理程序中。
    • Dan:是的,我的意思是一个事件处理程序。我的观点是 OP 似乎有一种情况,即他的代码没有与事件处理程序的代码耦合。我的意思是,为什么假设损害仅限于应用程序并且只会使应用程序崩溃?你不知道这个事件处理程序与哪些可怕的事情有关,那么为什么不让整个操作系统崩溃呢?
    猜你喜欢
    • 2014-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-09
    • 2021-02-25
    • 1970-01-01
    相关资源
    最近更新 更多