【问题标题】:Unhandled Exception not caught by AppDomain.UnhandledExceptionAppDomain.UnhandledException 未捕获到未处理的异常
【发布时间】:2016-10-06 15:45:56
【问题描述】:

我们有一个 .NET 3.5 程序集 (dll) 由 VB6“代理”(exe) 通过 COM 接口执行。 VB6 代码确实调用了:

' Ensure that no system dialog comes up when we GPF.
PreviousErrorMode = SetErrorMode(SEM_NOGPFAULTERRORBOX)
SetErrorMode PreviousErrorMode Or SEM_NOGPFAULTERRORBOX

' Assign our Exception Handler for this process.
PreviousExceptionFilter = SetUnhandledExceptionFilter(AddressOf UnhandledExceptionFilter)

VB6 UnhandledExceptionFilter 将异常转换为 VB6 引发的错误。 (但这里也没有调用它,而且这个 VB6 代码多年来没有被更改——甚至没有重新编译。)

在 VB6 代码创建的第一个 .NET 对象的构造函数中,我向AppDomain.CurrentDomain.UnhandledException 添加了一个处理程序(并且,为了调试此问题,我为Windows.Forms.Application.ThreadException 添加了一个处理程序,但它没有没有帮助,不是说我们有任何由 .NET 显示的 UI)。

我确定在 VS2008 中开发此代码时,UnhandledException 事件已经捕获了一些东西,但我不确定我在 VS2015 中是否见过它。

代码运行多个线程,其中一些执行主要目的,包括插入和更新 SQL Server 数据,还通过 TCP 协议进行通信,该协议由其他线程处理,包括 BackgroundWorker

执行主要目的的人是通过以下方式创建的:

taskThread = New Thread(AddressOf EnsureTaskCompletes)
taskThread.SetApartmentState(Threading.ApartmentState.STA)
taskThread.Start()

它太专有和复杂,无法向您展示更多代码,但我在调试此问题时已将代码调整为简单的Throw New Exception,而不是最终具有相同效果的复杂代码。(*)

UnhandledExceptionHandler 没有处理这个“简单”异常,除非我打开了非托管调试,否则它只会静默终止程序。

当 .NET Framework “仅”作为 dll 加载时,AppDomain.CurrentDomain.UnhandledException 的行为是否有记录更改?

(由于 COM 重新注册,我不想在 VS2008 中构建以前的版本,但尚未确认 是否有行为更改。我已经确认了 . NET 3.5 Exe 由 VS2015 编译,它创建一个抛出未处理异常的 Thread确实 处理 UnhandledException 事件中的异常。)

对我来说,解决方法是将Catch 添加到EnusreTaskCompletes 中的现有Try Finally 中,该EnusreTaskCompletes 已经尝试至少记录线程在任务完成之前已终止,然后我自己调用UnhandledExceptionHandler

(*实际的问题是数据库中缺少一个表,该表仅在您用尽信用时使用-我没有添加该代码或表。这引发了一个未被捕获的异常UnhandledExceptionHandler,调试起来相当困难——SQL Server Profiler 终于找到了问题。)

【问题讨论】:

标签: .net multithreading vb6 unhandled-exception


【解决方案1】:

这是一个极其广泛的话题,速度极快。不,当您的代码从 VB6 调用时,您永远不会观察到 UnhandledException 事件。跨互操作边界传递异常是严格verboten

CLR 符合要求,它使用 CCW 中的 catch-em-all 异常处理程序来捕获异常。并将其转换为符合 COM 标准的 HRESULT 错误代码。每个 .NET 异常都有一个不同的 Exception.HResult 属性值。

VB6 运行时又将它们转换为 VB6 错误。您需要使用On Error 语句来捕获它们。 CLR 实现 IErrorInfo 以提供 一些 信息,VB6 Err.Number 属性具有 Exception.HResult 值,Err.Description 为您提供 Exception.Message 属性值。但是,如果您需要 InnerException 属性来诊断故障,您将不会获得神圣的 Stacktrace,并且会非常不便。考虑使用 AppDomain.FirstChanceException 事件来记录详细信息。

调试这样的异常很容易。使用项目 > 属性 > 调试 > 启动外部程序单选按钮。选择已编译的 VB6 程序或 VB6 IDE。强制调试器在第一次出现异常时停止,在 VS2015 中使用 Debug > Windows > Exception Settings 并勾选“Common Language Runtime exceptions”。

请注意,此机制不适用于您在自己的工作线程上运行的任何代码。在互操作场景中处理这些问题非常棘手。大概这就是让您对“有时它有效”的情况感到困惑的原因。

使用 SetUnhandledExceptionFilter() 时要非常小心,使用它很容易破坏 .NET 代码。 CLR 也调用该函数来安装自己的处理程序并使用它来引发一些 异常。值得注意的是 NullReferenceException 和 DivideByZeroException,C# 代码可能想要自己捕获的那种异常。还有一些非常糟糕的东西,比如 AccessViolationException,你永远不想抓住它。顺便说一句,它也可能会干扰 VB6 错误,运行时也使用 SEH 来引发异常。

【讨论】:

    【解决方案2】:

    简短的(可能的)答案, 看起来,在没有附加调试器的情况下,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一旦完成就被踢,并且应用程序继续在异常状态下工作。我所做的最终解决方案是使用MyForm.Load += (s,e) => new Thread(()=>{/* My Form_Load code*/ }).Start(); 将所有代码从Form_load 移动到另一个线程。这样,Application.ThreadException 被触发,它被路由到 MyUnhandledExceptionHandler,我的安全出口。

    【讨论】:

      【解决方案3】:

      (这只是一个评论,尤其是因为它主要是把我的评论扩展到描述一个新问题的问题,但它变得太大了。)

      我至少已经重现了我在 所有 版本中看到的问题(即使在重置我的 Visual Studio 设置等*之后),在带有“抑制模块加载时的 JIT 优化(仅限托管)”已关闭,“仅我的代码”已打开。显然,这几乎是一次完全非调试的运行,并且它确实在此处警告不会命中断点,因为尚未加载符号。

      (实际上,我后来才注意到这个故障记录在即时窗口中,其中提到了“只是我的代码”。我确实主要看调试输出,并且肯定会关闭重定向到即时窗口window 选项,但它会被重置为 ON,所以我最初错过了它。)

      这让我注意到,在当前状态下,我有问题的解决方案的断点现在在运行时添加或启用时会显示感叹号三角形,但不会像新的 .NET 那样单独在执行时显示唯一的项目。

      (*)重启、重置我的设置、修复我的安装和删除我的 .suo 文件都没有帮助。

      我没有明确说明的一件事是,对于有问题的解决方案,我以管理员身份运行 VS2015(在 Windows 10 64 位上),因为它需要在编译结束时为 COM 注册程序集.当然,在发现任何这些调试问题之前,我一直都需要这个要求。

      我会在某个阶段尝试在 Win10 32 位笔记本电脑上进行调试,看看其中有多少是可重复的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-11-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-30
        • 1970-01-01
        • 2017-05-08
        • 1970-01-01
        相关资源
        最近更新 更多