【问题标题】:Event Exception Handling In Framework框架中的事件异常处理
【发布时间】:2011-10-13 20:41:49
【问题描述】:

我正在寻找有关处理事件异常的最佳做法的一些指导。目前,当我的应用程序中抛出异常时,异常消息会显示在一个弹出对话框中,然后应用程序在点击确定后重新启动。我看到的问题是一些第三方库的事件处理程序中发生了很多异常,并且这些异常被吞没并且从未显示,因为它们位于后台线程上。以下是不同人想到的一些解决方案,我想知道其中任何一个是最好的方法。

  1. 将后台线程转发到应用程序中每个事件处理程序中的 UI 线程。
  2. 将事件包装在另一个类中,该类对调用事件的每个方法都有一个 try/catch。如果发生异常,catch 会将异常转发给 UI 线程。
  3. 获取对第三方库的访问权限,并将 try/catch 放在调用事件的位置,然后可以通过全局事件将其转发到主应用程序。

【问题讨论】:

  • 发生了什么样的异常?如果它们被吞食了,你怎么知道它们?
  • 各种不同的,空引用,集合错误等。我知道它们正在发生,因为我在事件处理程序中抛出了 new Exception() 并且它没有在任何地方被捕获。

标签: c# wpf events c#-4.0 exception-handling


【解决方案1】:

以上都不是。而是将事件连接到 ApplicationAppDomain 以获取未处理的异常。

更多信息: WPF 应用程序Application.DispatcherUnhandledException 的全局异常处理事件仅针对主 UI 线程上抛出的异常触发。但是,AppDomain.CurrentDomain.UnhandledException 应该在 any 线程中的任何未处理的异常上触发(不幸的是,没有阻止应用程序在到达此处后关闭)。

对最佳实践进行了一些快速研究,发现建议您使用try\catch 块手动处理后台线程上的异常。阅读此页面上的异常部分 http://www.albahari.com/threading/ 并查看此 StackOverflow 问题 Catching unhandled exception on separate threads

【讨论】:

  • 那些事件是挂钩的,这就是我显示异常发生对话框的方式。当异常发生在后台线程时,这些事件都不会被触发。
  • 还挂钩到 TaskScheduler.UnobservedTaskException。任务异常不会像典型的线程异常那样传播,因为它们会被缓存以防继续处理异常。如果没有观察到它们(通过继续或等待任务完成),如果此事件未注册,它们最终将被任务的 finalizer 抛出(丑陋,我知道)。
  • 不错的丹。我不知道那个。
  • @Dennis,是的,他们用 TPL 偷偷加入了它。它以前被我咬过,而且特别讨厌,因为在故障任务上发生异常后,在某个不确定的时间在终结器线程上抛出一个未观察到的异常。
【解决方案2】:

让我们按照您列出的方式讨论您的选项:

1) 在每个事件中将后台线程转发给 UI 线程 应用程序中的处理程序。

你不应该也不能!因为:

  • 如果“可能来自第三方”的事件启动另一个后台线程,您不能假装 或计时器。
  • 拥有后台线程的重点不是 要冻结 UI,您将通过长时间的执行代码来处理用户界面。

2) 将事件包装在另一个类中,该类对调用事件的每个方法都有一个 try/catch。如果发生异常,catch 会将异常转发给 UI 线程。

  • 与上一点相同。你不能假装那个事件启动了另一个线程..

3) 获取对第三方库的访问权限,并将 try/catch 放在调用事件的位置,然后可以通过全局事件将其转发到主应用程序。

这是一个糟糕的选择,真的,你不应该更改第三方库的代码即使你可以。因为:

  • 您不是这些库的作者。
  • 每当您想要更新库版本甚至更改库时,您都必须重写那里的代码..

那么你必须做什么?答案是您目前正在做的某种事情:
每当抛出任何异常时,记录异常重新启动您的流程,并在您的流程中的某个时间点将记录的错误发送到您的电子邮件,然后开始修复它们。

【讨论】:

    【解决方案3】:

    如果您要挂钩 AppDomain.UnhandledException 事件,那么问题不在于它们可能会在后台线程上回调,而是第三方库明确吞下了处理程序抛出的异常。这是一个表现不佳的库,但是,由于您正在编写事件处理程序代码,您可以在事件处理程序中捕获异常并强制关闭应用程序。

    由于无法通过抛出异常来停止第三方库,因此可以通过调用强制线程终止:

    Thread.CurrentThread.Abort();

    Thread.Abort() 通常被认为是一种不好的做法,但在这里它的危险性略小,因为您正在中止自己,因此不会将异常注入到可能令人讨厌的位置(您还知道线程不在非托管上下文,所以它会立即中止。)它仍然是一个讨厌的黑客,但第三方应用程序并没有给你太多选择。 ThreadAbortException 不能被“停止”,因此第三方代码将在它到达其异常处理程序的末尾时立即终止其线程(即使他们试图吞下它)。如果第三方库真的很讨厌,它可能会在其 catch 或 finally 块中调用大量代码,甚至使用肮脏的技巧来维持生命,就像启动其他线程一样,但这样做必须非常恶意。

    要获得友好的错误消息行为,您可以使用 SynchronizationContext 或 Dispatcher 将异常编组到 UI 线程以调用异常处理程序显示(最简单的方法可能是重新抛出异常;我建议嵌入它作为 InnerException,这样您就不会丢失堆栈跟踪。)

    如果你真的不信任这个库(并且不想让它有机会通过它的 catch 和 finally 阻塞来保持活力),你可以使用阻塞调用显式编组到 UI 线程(以显示你的错误消息以友好的方式)然后调用Environment.FailFast() 来终止你的进程。这是一种特别苛刻的终止方式(它不会调用任何清理代码,并且会尽快终止进程),但如果应用程序状态变得非常损坏,它可以最大限度地减少任何潜在的破坏性副作用的可能性。这里要注意的一件可能的事情是,UI 线程可能被与第三方代码相关的其他一些 UI 调用阻塞。如果发生这种情况,应用程序将死锁。同样,这对它来说是一件非常恶意的事情,所以你可以忽略它。

    【讨论】:

      猜你喜欢
      • 2012-06-10
      • 2014-07-11
      • 2021-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-27
      • 1970-01-01
      相关资源
      最近更新 更多