CLR在非CLR创建的线程中承载异常处理

CLR hosting exception handling in a non-CLR-created thread

本文关键字:CLR 异常处理 线程 在非 创建      更新时间:2023-10-16

问题:

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

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

  • "1"在调用线程(非CLR创建的线程)中引发异常
  • "2"在新的Thread()(CLR创建的线程)中引发异常

在"1"的情况下

  • 从未调用CurrentDomain_UnhandledException()
  • 应用程序域和进程将保持加载和运行状态,您只会得到一个FAILED

在情况"2"(预期行为)

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

问题:

要获得"正常"行为,必须做些什么?

示例代码:

以下代码基于Visual Studio 2010"CppHostCLR"代码示例来自"所有互操作和融合样本"。

运行时主机(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%08lxn", 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创建的线程中,未处理的异常应该或多或少地表现得"自然"——请参阅"托管线程中的异常"

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

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

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

进一步的研究使我找到了"CLR中未处理的异常处理",在那里我发现了以下内容:

"如果未在托管方法中处理异常…,则异常将退出CLR,但继续作为本机SEH异常在堆栈中向上传播(托管异常表示为本机SEH异常)。。。操作系统未处理异常筛选器(UEF)机制可能并不总是导致触发CLR的未处理异常处理。在正常情况下,这将按预期工作,并且CLR的未处理异常处理将被触发。然而,在某些情况下,这种情况可能不会发生。"

上面的代码有什么问题,或者如何更改它以触发CLR未处理的异常处理?

更新(2011-05-31):

我刚刚发现了一个旧的错误报告,"当从非托管代码调用托管代码并抛出异常时,不调用UnhandledExceptionEventHandlerhttp://tinyurl.com/44j6gvu",微软确认这是一个"错误"行为:

感谢您花时间报告此问题 该行为实际上是由CLR执行引擎和CRT竞争UnhandledExceptionFilter引起的错误。CLR的体系结构已在支持此场景的4.0版本中进行了修订。

更新(2011-06-06):

为什么做好这件事很重要?

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

注意:通过SetActionOnFailure()更改CLR行为会使情况变得更糟,从某种意义上说,它最终会屏蔽原始异常(即,您最终看到的不是内存不足,而是线程中止-不知道原始错误来自哪里)!

更新意味着您可能有一个解决方案。然而,它的解决方案不会在所有情况下都起作用,所以这里有更多信息。

如果您喜欢CLR Unhandled Exception行为,那么将其作为最外层的程序,并仅调用本机代码来执行特定的函数。这将确保CLR能够控制未处理的异常筛选器。

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

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

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

Martyn