CLR在非CLR创建的线程中托管异常处理

arc*_*hgl 17 .net c++ clr exception-handling clr-hosting

问题:

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

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

  • "1"在调用线程(非CLR创建的线程)中抛出异常,
  • "2"在新的Thread()(CLR创建的线程)中抛出异常.

在"1"的情况下

  • 永远不会调用CurrentDomain_UnhandledException().
  • 应用程序域和进程将保持加载和运行的,你只会得到一个失败.

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

  • 调用CurrentDomain_UnhandledException().
  • 这个过程被杀了.

问题:

要做出"正常"行为需要做些什么?

示例代码:

下面的代码基于" 所有互操作和融合样本 "中的Visual Studio 2010" CppHostCLR "代码示例.

RuntimeHost(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;
}
Run Code Online (Sandbox Code Playgroud)

托管代码(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"");
    }
Run Code Online (Sandbox Code Playgroud)

迄今为止的研究:

MSDN最初暗示非CLR创建的线程中的未处理异常应该或多或少"自然" - 请参阅" 托管线程中的异常 "

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

"Most"意味着在CLR创建的线程内部,线程中止和Application Domain卸载异常的处理方式不同.在非CLR线程中

"他们正常进行,导致申请终止."

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

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

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

更新(2011-05-31):

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

感谢您抽出宝贵时间报告此问题.该行为确实是由CLR执行引擎和CRT竞争UnhandledExceptionFilter引起的错误.CLR的体系结构已在支持此方案的4.0版本中进行了修订.

更新(2011-06-06):

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

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

注意:改变CLR行为SetActionOnFailure()会使事情变得更糟,因为它最终掩盖了原始异常(即,而不是内存不足,你最终看到threadAborts - 没有线索原始错误来自哪里)!

Mar*_*ell 2

更新意味着您可能在这里找到了解决方案。然而,它的解决方案并不适用于所有情况,所以这里有一些更多信息。

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

如果您想保留当前的结构并且您拥有的 C++ 代码很小,您可以完全停止使用 CRT(这将拒绝您使用许多有用的东西,包括静态构造函数和 C++ 异常处理)。这将确保 CLR 获取未处理的异常过滤器。

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

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

马丁