【问题标题】:WinForm "global" catch exception for MDI child forms "only"WinForm“全局”捕获 MDI 子窗体“仅”的异常
【发布时间】:2017-08-29 20:20:46
【问题描述】:

我有一个 MDI 父/子应用程序。
在 Program.cs 文件中,我有一个针对 ThreadException 和 UnhandledException 的全局异常处理程序。

那些工作正常。

当我在全局级别收到未处理的异常时,在 UnhandledException 处理程序中,我调用 Environment.Exit(1) 来关闭应用程序,因为我不知道应用程序的当前状态。

在子表单中,我“通常”将以下内容添加到事件处理程序中。

try
{
    // Some Code
}
catch (Exception ex)
{
    HandleException(ex);
    MessageBox.Show("Some message");
    this.Close();
}

我想知道是否有一种方法可以为我的所有子窗体(我确实有一个它们继承自的基本窗体)添加一个全局异常处理程序,该处理程序可以捕获异常并关闭子窗体而无需关闭整个应用程序。
这样,如果开发人员“忘记”在事件中添加 try catch 块,它不会轰炸整个应用程序。

【问题讨论】:

  • 我同意故障转储会很方便。我确实有一个全局异常记录器,它保存堆栈跟踪、一些性能设置等......它们保存在 sql 数据库中以供查看,但我不想仅仅因为有人忘记添加 try catch 就杀死应用程序就像将字符串放在数字文本框中一样简单。

标签: c# winforms exception-handling


【解决方案1】:

我同意这通常是一个坏主意,但如果您的全局异常处理程序有一个“发送者”对象,您可以尝试使用sender.GetType() 来获取对象的类型。您可以通过说if (sender.GetType().IsSubclassOf(typeof (BaseClass))) { // then do something } 来查看它是否继承自您的基本形式。

如果发送者是子窗体的子控件(而不是窗体本身),那么您可能需要使用辅助方法向上走几级祖先才能找到父窗体(例如:发送者可能是文本框,sender.Parent 可能是一个面板,sender.Parent.Parent 可能是表单)。您可以递归地检查父级,直到父级为空,或者父级派生自您的基类。

您还可以循环访问 MdiParent 表单的 MdiChildren 以查看其中是否有任何一个是导致异常的表单。那么你就不需要从基类继承了。

此外,在 ThreadException 的情况下,如果您在即发即弃类型的线程中有一些匿名方法,这可能不起作用...

我只在一个项目中使用全局异常处理,而且我似乎记得它在调试模式下无法正常工作,因为您获得了 Visual Studio 异常帮助程序,所以我没有快速的测试方法- 只需将其视为一般的“伪代码”建议......

【讨论】:

  • 当然,如果您启动一个线程并且您不希望它再次与您的表单交互,那么理论上您可以吞下异常并且表单将继续运行(再次 - 创建一个无效状态、数据损坏等的巨大潜力,但你可以做到)。如果您的表单正在等待线程完成执行,那么您必须在该级别捕获错误(通过有意义的超时并意识到预期的操作从未完成。)
  • 您可以使用 System.Diagnostics 以编程方式访问堆栈跟踪。 string fileName = new StackTrace().GetFrame(1).GetFileName(); 会为您提供第 1 帧的文件名 - 您可以沿着帧向上走,还有其他方法,例如 GetFileName() 用于获取方法名称、类型等。
  • 在你的帮助下,我离得很近了。我最终在“new StackTrace(exception, true).GetFrames()”上使用了一个 foreach 循环,然后使用“bool cancelExit = stackFrame.GetMethod().ReflectedType.IsSubclassOf(typeof(Project.BaseForm));”但是后来我发现,由于我没有/无法获取崩溃的表单实例,我无法关闭/处理该损坏的表单......如果我能弄清楚如何获取的句柄 ID表单,我可以向它发送一条 Windows 消息来关闭该表单。还在挖。
  • 如果您有子窗体的名称或类型,您可以循环遍历 MDI 容器的 MDI 子级来查找它,如果您打开了 6 个相同的窗体,这可能会更难。您的方法的问题是,如果您的子表单在每次单击“保存”时在 SQL 查询中崩溃,用户会认为数据已保存,而您的数据将不一致。开发人员将无法调试问题,因为您正在吞下异常。我认为更好的做法是使用全局异常处理来记录错误,然后仍然允许程序崩溃。
  • 我绝不会推荐吃例外。应该记录所有异常,然后向调用者显示一些错误通知。我只想杀死表单而不是应用程序。无论如何,您给我的信息非常有帮助,但我得出的结论是,Microsoft 没有提供一种简单的方法来在表单级别捕获未处理的异常,而无需在所有事件处理程序上使用 try catch 块。我也知道在所有事件上添加带有异常处理程序的 try catch 块是最佳实践,但有时开发人员会忘记...
【解决方案2】:

正如我在 cmets 中已经说过的那样,我不太喜欢你的方法,尤其是在你捕捉到最普遍的异常的情况下。

该消息只能部分帮助您解决问题。崩溃可以帮助你彻底解决问题,包括调用堆栈、堆上的对象、CPU 寄存器等。只需学习How to take a crash dump 并分析它。通过关闭 MDI 表单,您忽略了应用程序的数据可能处于无效状态这一事实

但是好吧,那是编码风格,也许不应该在这里讨论。

你能做的就是Aspect Oriented Programming (APO) [Wikipedia]。方面是适用于代码的多个部分的东西,您不想在任何地方显式地实现它。

您将方面实现为代码,然后在配置文件中定义您希望应用该代码的位置,例如根据命名约定。

魔法发生在编译时。在您的普通代码编译完成后,中间代码编织器 (IL-weaver) 会修改 IL 代码并在任何地方插入切面。

您需要像PostSharp(商业)或AfterThought(免费)这样的库。 PostSharp 甚至还有examples for exception handling,可以很方便地用来显示消息框。

当你第一次尝试它时,它需要一些思考,所以在你将这个概念应用到你的代码之前,一定要遵循一些教程。

【讨论】:

  • 我同意您的观点,即在发生未处理的异常时关闭 MDI 子程序并让应用程序继续运行是不好的做法,但在此应用程序中,所有 MDI 表单都是相互独立的,并且没有全局 /应用程序中的静态方法/属性/变量。所以在这个应用程序中,杀死一个表单而不杀死整个应用程序是安全的。但我确实喜欢您发送的链接/信息,并将阅读它们以备将来使用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-19
  • 2016-03-19
  • 1970-01-01
  • 2020-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多