31

我正在维护一个 .NET 1.1 应用程序,我的任务之一是确保用户看不到任何不友好的错误通知。

我已经向Application.ThreadExceptionand添加了处理程序AppDomain.CurrentDomain.UnhandledException,它们确实被调用了。我的问题是仍然显示标准 CLR 错误对话框(在调用异常处理程序之前)。

Jeff 在他的博客herehere上谈到了这个问题。但是没有解决办法。那么.NET 1.1 中处理未捕获异常和显示友好对话框的标准方法是什么?

Jeff 的回答被标记为正确答案,因为他提供的链接包含有关如何执行所需操作的最完整信息。

4

6 回答 6

13

哦,在 Windows 窗体中,您绝对应该能够让它工作。您唯一需要注意的是发生在不同线程上的事情。

我在这里有一篇旧的代码项目文章,应该会有所帮助:

用户友好的异常处理

于 2008-08-04T04:31:02.557 回答
5

AppDomain.UnhandledException是一个事件,而不是全局异常处理程序。这意味着,当它被提出时,您的应用程序已经处于下水道,除了进行清理和错误记录之外,您无能为力。

幕后发生的事情是这样的:框架检测到异常,将调用堆栈向上走到最顶部,没有找到可以从错误中恢复的处理程序,因此无法确定继续执行是否安全。因此,它启动了关闭序列并启动了这个事件,作为对你的礼貌,这样你就可以对你已经注定的过程表示敬意。当在主线程中未处理异常时会发生这种情况。

这种错误没有单点解决方案。您需要在发生此错误的所有位置的上游放置一个真正的异常处理程序(一个 catch 块),并将其转发到(例如)一个全局处理程序方法/类,该方法/类将确定是否可以安全地简单地报告并继续,基于异常类型和/或内容。

编辑:可以禁用(=hack)Windows 内置的错误报告机制,因此当您的应用程序出现故障时,不会显示强制性的“崩溃和刻录”对话框。但是,这对系统中的所有应用程序都有效,而不仅仅是您自己的应用程序。

于 2008-08-04T10:20:01.260 回答
5

.NET 1.x Windows 窗体应用程序中未处理的异常行为取决于:

  • 引发异常的线程类型
  • 是否在窗口消息处理过程中发生
  • 调试器是否附加到进程
  • DbgJitDebugLaunchSetting 注册表设置
  • App.Config 中的 jitDebugging 标志
  • 是否覆盖 Windows 窗体异常处理程序
  • 是否处理了 CLR 的异常事件
  • 月相

未处理异常的默认行为是:

  • 如果在泵送窗口消息时在主线程上发生异常,它会被 Windows 窗体异常处理程序拦截。
  • 如果在泵送窗口消息时在主线程上发生异常,它将终止应用程序进程,除非它被 Windows 窗体异常处理程序拦截。
  • 如果异常发生在手动、线程池或终结器线程上,则会被 CLR 吞下。

未处理异常的联系点是:

  • Windows 窗体异常处理程序。
  • JIT 调试注册表开关 DbgJitDebugLaunchSetting。
  • CLR 未处理的异常事件。

默认情况下,Windows 窗体内置异常处理执行以下操作:

  • 在以下情况下捕获未处理的异常:
    • 异常在主线程上并且没有附加调试器。
    • 窗口消息处理期间发生异常。
    • App.Config 中的 jitDebugging = false。
  • 向用户显示对话框并防止应用程序终止。

您可以通过设置 jitDebugging = true 来禁用后一种行为App.Config。但请记住,这可能是您停止应用程序终止的最后机会。因此,捕获未处理异常的下一步是注册事件 Application.ThreadException,例如:

Application.ThreadException += new
Threading.ThreadExceptionHandler(CatchFormsExceptions);

请注意 HKEY_LOCAL_MACHINE\Software.NetFramework 下的注册表设置 DbgJitDebugLaunchSetting。这具有我知道的三个值之一:

  • 0:显示询问“调试或终止”的用户对话框。
  • 1:让异常通过让CLR来处理。
  • 2:启动 DbgManagedDebugger 注册表项中指定的调试器。

在 Visual Studio 中,转到菜单工具选项调试JIT将此键设置为 0 或 2。但值 1 通常在最终用户的计算机上最好。请注意,此注册表项在 CLR 未处理异常事件之前执行。

这最后一个事件是您记录未处理异常的最后机会。它在您的 finally 块执行之前触发。您可以按如下方式拦截此事件:

AppDomain.CurrentDomain.UnhandledException += new
System.UnhandledExceptionEventHandler(CatchClrExceptions);
于 2008-09-20T15:52:49.377 回答
3

这是控制台应用程序还是 Windows 窗体应用程序?如果它是一个 .NET 1.1 控制台应用程序,很遗憾,这是设计使然——MSFT 开发人员在您引用的第二篇博文中证实了这一点:

顺便说一句,在我的 1.1 机器上,来自 MSDN 的示例确实具有预期的输出;只是在您附加调试器(或不附加)之后,第二行才会显示。在 v2 中,我们进行了翻转,以便在调试器附加之前触发 UnhandledException 事件,这似乎是大多数人所期望的。

听起来 .NET 2.0 在这方面做得更好(谢天谢地),但老实说,我从来没有时间回去检查。

于 2008-08-04T02:45:07.717 回答
1

这是一个 Windows 窗体应用程序。Application.ThreadException 捕获的异常工作正常,我没有得到丑陋的 .NET 异常框(OK终止,Cancel调试?谁想出了这个??)。

我得到了一些没有被它捕获的异常,最终导致了导致问题的 AppDomain.UnhandledException 事件。我想我已经捕获了大部分异常,现在我将它们显示在我们漂亮的错误框中。

所以我只希望没有其他情况会导致 Application.ThreadException 处理程序无法捕获异常。

于 2008-08-04T02:54:15.683 回答
0

简短的回答, 看起来,Form.Load 中发生的异常在没有附加调试器的情况下不会路由到 Application.ThreadException 或 AppDomain.CurrentDomain.UnhandledException。

更准确的答案/故事 这就是我解决类似问题的方法。我不能确定它是如何做到的,但这是我的想法。欢迎提出改进建议。

三个事件,

  1. AppDomain.CurrentDomain.FirstChanceException
  2. AppDomain.CurrentDomain.UnhandledException
  3. 和 Application.ThreadException

累积捕获大多数异常,但不在全局范围内(如前所述)。在我的一个应用程序中,我结合使用这些来捕获各种异常,甚至是非托管代码异常,如 DirectX 异常(通过 SharpDX)。所有异常,无论是否被捕获,似乎都在毫无疑问地调用 FirstChanceException。

AppDomain.CurrentDomain.FirstChanceException += MyFirstChanceExceptionHandler;
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); // not sure if this is important or not.
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; // can't use Lambda here. need to Unsub this event later.
Application.ThreadException += (s, e) => MyUnhandledExceptionHandler(e.Exception);

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    MyUnhandledExceptionHandler((Exception)e.ExceptionObject);
}
private void CurrentDomain_FirstChanceException(object sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs eventArgs)
{
    // detect the pattern of the exception which we won't be able to get in Fatal events.
    if (eventArgs.Exception.Message.StartsWith("HRESULT"))
        MyUnhandledExceptionHandler(eventArgs.Exception);
}

处理程序看起来像

static void MyUnhandledExceptionHandler(Exception ex)
{
    AppDomain.CurrentDomain.UnhandledException -= MyUnhandledExceptionHandler;  // this is important. Any exception occuring in the logging mechanism can cause a stack overflow exception which triggers the window's own JIT message/App crash message if Win JIT is not available.
    // LogTheException()
    // Collect user data
    // inform the user in a civil way to restart/close the app
    Environment.Exit(0);
}

像 DirectX 异常这样的非托管代码异常只出现在 FirstChanceException 中,我必须自己决定异常是否致命。然后我使用 MyUnhandledExceptionHandler 进行记录,并以友好的方式让用户知道一切都在“控制之下”。

重要的提示! 该方案仍然没有捕获一种异常。它确实出现在 FirstChanceException 中,但很难将它与其他类型的异常处理此处理程序区分开来。在 Form.Load 中直接发生的任何异常都有这种不同的行为。当附加 VS 调试器时,这些被路由到 UnhandledException 事件。但是如果没有调试器,则会弹出一条老式的 windows 消息,显示发生的异常的堆栈跟踪。最烦人的是,它并没有让MyUnhandledExceptionHandlerr一旦完成就被踢,并且应用程序继续在异常状态下工作。我所做的最终解决方案是将所有代码从 Form_load 移动到另一个线程使用MyForm.Load += (s,e) => new Thread(()=>{/* My Form_Load code*/ }).Start();. 这样,Application.ThreadException 被触发,它被路由到 MyUnhandledExceptionHandler,我的安全出口。

于 2022-01-28T06:42:56.517 回答