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

发布于 2024-11-09 18:56:37 字数 3707 浏览 0 评论 0原文

问题:

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

在下面的代码中,使用

  • “1”从 C++ 调用 CSSimpleObject.GetstringLength() 会在调用线程(非 CLR 创建的线程)中引发异常,
  • “2”会在新线程中引发异常( )(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;
}

托管代码 (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):

我刚刚发现了一个旧的错误报告,“当从非托管调用托管代码并引发异常时,不会调用 UnhandledExceptionEventHandler - http://tinyurl.com/44j6gvu”,其中微软确认这是一种“有缺陷”的行为:

感谢您花时间报告此问题。 该行为确实是 CLR 执行引擎和 CRT 争夺 UnhandledExceptionFilter 引起的错误。 4.0版本中对CLR的架构进行了修改,支持该场景。

更新(2011-06-06):

为什么正确处理这一点很重要?

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

注意:更改 CLR 行为从某种意义上说,通过 SetActionOnFailure() 使事情变得更糟,它最终掩盖了原始异常(即,您最终看到的不是内存不足,而是 threadAborts - 不知道原始错误来自何处) !

The issue:

An unhandled exception in a thread entering the CLR from unmanaged code does not trigger the "normal" unhandled exception CLR processing.

In the code below calling CSSimpleObject.GetstringLength() from C++ with

  • "1" throws an exception in the calling thread (non-CLR-created thread),
  • "2" throws an exception in a new Thread() (CLR-created thread).

In case "1"

  • CurrentDomain_UnhandledException() is never called.
  • The Application Domain and the process will stay loaded and running, you will only get a FAILED.

In case "2" (expected behavior)

  • CurrentDomain_UnhandledException() is called.
  • The proces gets killed.

The Question:

What has to be done to get the "normal" behavior?

Sample code:

The code below is based on the Visual Studio 2010 "CppHostCLR" code sample
from the "all interop and fusion samples".

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;
}

Managed Code (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"");
    }

Research so far:

MSDN initially implies that unhandled exceptions in a non-CLR-created thread should behave more or less "naturally" - see "Exceptions in Managed Threads"

The common language runtime allows most unhandled exceptions in threads to proceed naturally. In most cases this means that the unhandled exception causes the application to terminate."

"Most" meaning that in CLR-created threads internal, thread abort and Application Domain unloaded exceptions are handled differently. In non-CLR threads

"they proceed normally, resulting in termination of the application."

Further research led me to "Unhandled Exception Processing In The CLR" where I found out the following:

"if the exception was not handled ... in the managed method, the exception would exit the CLR but continue to propagate up the stack as a native SEH exception (managed exceptions are represented as native SEH exceptions) ...
The OS unhandled exception filter (UEF) mechanism may not always result in triggering the CLR's unhandled exception processing.
Under normal circumstances, this will work as expected and the CLR's unhandled exception processing will be triggered. However, in certain instances this may not happen."

What is wrong with the code above or how can it be changed so that the CLR's unhandled exception processing is triggered?

Update (2011-05-31):

I just found an old bug report, "UnhandledExceptionEventHandler is not called when managed code is called from unmanaged and throws an exception - http://tinyurl.com/44j6gvu", where Microsoft confirms this is a "buggy" behavior:

Thank you for taking the time to report this problem. The behavior is indeed a bug caused by the CLR execution engine and the CRT competing for the UnhandledExceptionFilter. The architecture of the CLR has been revised in the 4.0 version supporting this scenario.

Update (2011-06-06):

Why is it important to get this right?

  • if you are creating a hosting-environment your developers expect a consistent behavior in the exception handling
  • unless there is a way to trigger the "normal CLR exception handling" in a native thread it means you always have to transfer execution to a managed thread (enqueueing in a thread pool for instance)
  • there is still that tiny bit of code transfering excecution from native to managed thread ... that has to catch all exceptions and somehow handle that situation differently

Note: changing the CLR behavior through SetActionOnFailure() makes matters worse, in the sence, that it ends up masking the original exception (i.e. instead of an out of memory you endup seeing threadAborts - without a clue where the original error cam from)!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

子栖 2024-11-16 18:56:38

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

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

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

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

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

马丁

The update implies that you perhaps have a solution here. However, its solution won't work in all cases, so here is some more info.

If you prefer CLR Unhandled Exception behaviour, then make that the outer-most program and call native code only to perform specific functions. That will ensure that the CLR gets control of the unhandled exception filter.

If you want to retain your current structure and the C++ code you have is small, you could stop using the CRT at all (which will deny you a bunch of useful stuff including static constructors and C++ exception handling). That will ensure that the CLR gets the Unhandled Exception Filter.

And, of course, you could simply call SetUnhandledExceptionFilter youself and get the behaviour you want.

However, I think the best recommendation in this situation is to put an actual function with a catch block on the call stack of any thread where you want to do something when an exception happens and not rely on the UEF mechanism -- because in the context of a component system, it is always fragile as multiple users compete for it.

Martyn

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文