【问题标题】:CLR hosting exception handling in a non-CLR-created threadCLR 在非 CLR 创建的线程中托管异常处理
【发布时间】:2011-09-01 18:00:26
【问题描述】:

问题:

从非托管代码进入CLR 的线程中的未处理异常不会触发“正常”未处理异常 CLR 处理。

在下面的代码中,使用 C++ 调用 CSSimpleObject.GetstringLength()

  • “1”在调用线程(非CLR创建的线程)中抛出异常,
  • “2”在 new Thread()(CLR 创建的线程)中引发异常。

如果是“1”

  • CurrentDomain_UnhandledException() 永远不会被调用。
  • Application Domain 和进程将保持加载并运行,您只会收到 FAILED。

情况“2”(预期行为)

  • CurrentDomain_UnhandledException() 被调用。
  • 进程被杀死。

问题:

必须做什么才能获得“正常”行为?

示例代码:

以下代码基于 Visual Studio 2010“CppHostCLR”代码示例 来自“all interop and fusion samples”。

运行时主机(C++):

PCWSTR pszStaticMethodName = L"GetStringLength";
PCWSTR pszStringArg = L"1";
//PCWSTR pszStringArg = L"2";

hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(pszAssemblyPath,
    pszClassName, pszStaticMethodName, pszStringArg, &dwLengthRet);
if (FAILED(hr))
{
    wprintf(L"Failed to call GetStringLength w/hr 0x%08lx\n", hr);
    goto Cleanup;
}

托管代码 (C#):

public class CSSimpleObject
{
    public CSSimpleObject()
    {
    }
    //------8<----------
    public static int GetStringLength(string str)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

        switch (str)
        {
            case "1":
                throw new Exception("exception in non-CLR-created thread");
            case "2":
                Thread thread = new Thread(new ThreadStart(WorkThreadFunction));
                thread.Start();
                break;
        }
        return str.Length;

    }
    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Console.WriteLine("CurrentDomain_UnhandledException:" + e.ToString());
        Console.ReadKey();
    }
    public static void WorkThreadFunction()
    {
        throw new Exception(""exception in CLR-created thread"");
    }

研究至今:

MSDN 最初暗示非 CLR 创建的线程中未处理的异常应该或多或少“自然”地表现 - 请参阅“Exceptions in Managed Threads

公共语言运行时允许线程中大多数未处理的异常自然地进行。在大多数情况下,这意味着未处理的异常会导致应用程序终止。”

“大多数”意味着在 CLR 创建的内部线程中,线程中止和应用程序域卸载异常的处理方式不同。在非 CLR 线程中

“它们正常进行,导致应用程序终止。”

进一步的研究使我找到了“Unhandled Exception Processing In The CLR”,在那里我发现了以下内容:

"如果在托管方法中未处理异常...,该异常将退出 CLR,但继续作为本机 SEH 异常向上传播(托管异常表示为本机 SEH 异常)... OS 未处理异常过滤器 (UEF) 机制可能并不总是导致触发 CLR 的未处理异常处理。 在正常情况下,这会按预期工作,并会触发 CLR 的未处理异常处理。但是,在某些情况下,这可能不会发生。”

上面的代码有什么问题或者怎么改才能触发CLR的未处理异常处理?

更新(2011-05-31):

我刚刚发现了一个旧的错误报告,“当从非托管调用托管代码并引发异常时,未调用 UnhandledExceptionEventHandler - http://tinyurl.com/44j6gvu”,Microsoft 确认这是一个“错误”行为:

感谢您抽出宝贵时间报告此问题。 该行为确实是由 CLR 执行引擎和 CRT 竞争 UnhandledExceptionFilter 引起的错误。 CLR 的架构在支持该场景的 4.0 版本中进行了修改。

更新(2011-06-06):

为什么正确地做到这一点很重要?

  • 如果您正在创建托管环境,您的开发人员希望在异常处理中具有一致的行为
  • 除非有办法在本机线程中触发“正常 CLR 异常处理”,否则这意味着您必须始终将执行转移到托管线程(例如在线程池中排队)
  • 仍有一小部分代码将执行从本机线程转移到托管线程...必须捕获所有异常并以不同方式处理这种情况

注意: 通过 SetActionOnFailure() 更改 CLR 行为会使事情变得更糟,因为它最终会掩盖原始异常(即,您最终会看到 threadAborts 而不是内存不足 - 没有原始错误凸轮的线索)!

【问题讨论】:

  • 想知道您是否能够找到解决上述问题 #2 的方法,我遇到了类似的情况

标签: .net c++ exception-handling clr clr-hosting


【解决方案1】:

更新意味着您可能在这里找到了解决方案。但是,它的解决方案并非在所有情况下都有效,因此这里有更多信息。

如果您更喜欢 CLR 未处理异常行为,则将其设为最外层程序并仅调用本机代码以执行特定功能。这将确保 CLR 控制未处理的异常过滤器。

如果你想保留你当前的结构并且你的 C++ 代码很小,你可以完全停止使用 CRT(这会让你失去一堆有用的东西,包括静态构造函数和 C++ 异常处理)。这将确保 CLR 获得未处理的异常过滤器。

当然,您可以简单地自己调用 SetUnhandledExceptionFilter 并获得您想要的行为。

但是,我认为在这种情况下,最好的建议是在任何线程的调用堆栈上放置一个带有 catch 块的实际函数,当异常发生时你想在其中执行某些操作,而不依赖于 UEF 机制——因为在组件系统的上下文中,它总是很脆弱,因为多个用户竞争它。

马丁

【讨论】:

  • 对不起,更新误导了 - 我刚刚添加,因为微软的确认,该行为是一个错误。如果您看到原始问题,我正在寻找的是如何更改代码,以便触发正常的“clr”终止过程(调用未处理的异常)。在“CLR 中的未处理异常处理”中,作者解释了这种“错误”行为与 UEF 机制和过滤器的设置方式有关。所以问题是我必须如何设置它们?
  • 我们实际上有一个解决方法:1)在本机线程的方法调用中尝试/捕获所有内容 2)在工作线程池中作为操作入队。只有最少的代码可能出错并且不会触发“正常 clr”终止过程。其余代码由 clr“正常”处理。
  • 我喜欢你的第一个。只是不要在 /EHa C++ 代码中 __try/__catch all。
  • 一切都在本机线程调用的托管代码中。在 C++ 中,甚至没有由 com 管道完成的 try/catch,您可以在其中检查每个 HRESULT 代码的失败/成功。 managed 中的包罗万象是必要的,因为没有其他方法可以控制异常......除非有人能告诉我如何:)。
猜你喜欢
  • 2011-12-15
  • 1970-01-01
  • 2011-04-25
  • 2012-01-31
  • 1970-01-01
  • 1970-01-01
  • 2011-07-04
  • 2018-10-09
  • 1970-01-01
相关资源
最近更新 更多