回答这个问题时,我很惊讶子线程中的堆栈溢出会杀死整个进程。
其实,问题是:
为什么会这样?毕竟,每个线程都有自己的堆栈,据我所知,Richter 甚至写过它。
甚至StackTrace().FrameCount在主线程和子线程中显示不同数量的帧。
根据上一个问题中的评论,在 C++ 中,这是一个已处理的异常,但据.NET我所知,它可能不是因为堆栈短缺而导致需要做某事的 CLR 损坏。
CLR,理论上,一个用于整个应用程序,它绝对不会在子线程中旋转。
事实证明,当运行其他人的代码时,即使在单独的线程中,即使在单独的域中,我们也无法访问其源代码,我们仍然会失败,而在 .NET 中,这种类型无法防止这种情况发生例外?
UPD
有一些 CER“受限制的执行区域”,您可以在其中指定方法可以被进程信任。这对 CLR 是否有任何影响,以便它准备好并且不会死亡?
在我看来,CLR 工作的两个特征在这里重叠。
子线程中未处理的异常会杀死进程
不仅是堆栈溢出,而且通常子线程中任何未处理的异常都会杀死整个进程:
这在 MSDN 中有说明:
Eric Lippert 给出了这种行为的 [有争议的] 动机:
这意味着需要严格处理线程中的异常,如下所示:
无法捕获 StackOverflowException
但是StackOverflowException的特点是它无法被捕获,无论是在子线程中还是在其他任何地方:
异常文档是怎么说的:
该文档清楚地表明,在进程的任何地方,任何地方都不应该有堆栈溢出。
在某些退化的情况下,仍然可以处理 StackOverflowException:
在 .Net 1.0 中,堆栈溢出可能会被捕获,从 2.0 版本开始,开发人员采取了强硬立场:“堆栈溢出是程序员及其代码的问题,而不是 CLR。” 我找不到任何直接迹象表明为什么需要进行更改。我认为 SOE 之后的恢复没有问题,并且开发人员决定不在此任务上花费资源。
总的来说,CLR 的这两个特性都非常符合清教徒式的哲学立场“死程序不会说谎”,Eric Lippert 也涵盖了这一点:
更新:受约束的执行区域
评论中的问题:
影响。CER允许您保护自己免受代码执行期间可能发生的一些错误(类加载错误、内存不足)并正确释放资源。
但是,CER 无法处理堆栈溢出。Richter 写道(CLR via C#, Constrained Execution Regions):
那。使用单个 CER 捕获堆栈溢出将不起作用。文档中还特别提到了 StackOverflowException :
我无法获得ExecuteCodeWithGuaranteedCleanup方法来处理堆栈溢出。根据有关此方法的问题中的讨论,它也仅在您自己托管 CLR 时才有效。
链接
事实上,一切都有些不同。当然,标准 C++ 工具无法处理堆栈溢出。但是,在 Windows 上,它可以使用 SEH 机制来处理。而且,无论 Eric Lippert 说什么,从堆栈溢出中恢复是一个完全受支持的场景,否则为什么会存在_resetstkoflw和SetThreadStackGuarantee函数?
在 .NET 中,不处理 StackOverflowException 不是因为它在技术上是不可能的,而是因为开发人员决定这样做。在 Windows 上,堆栈溢出会引发 STATUS_STACK_OVERFLOW (0xC00000FD) SEH 异常。CLR 捕获 SEH 异常,如果它看到此代码,则强制终止进程(使用默认选项加载)。同时,出于某种原因,更危险的 Access Violation .NET 允许处理。
仅通过 .NET 是不可能的。然而,在非托管代码中,实际上几乎没有什么可写的。
解决此问题的一种方法是创建一个自定义非托管 DLL,其唯一目的是处理 SEH 异常并将其代码更改为 CLR“不会吓到”的代码(具有未知代码的 SEH 异常由 CLR 转换为一个可以处理的 SEHException)。在 C# 应用程序中,加载 DLL,设置向量异常处理程序,并使用 SetThreadStackGuarantee 函数增加保留堆栈区域的大小。
当然,这并不能完全恢复堆栈,也就是说,您可以再次在同一个线程中捕获堆栈溢出并处理它。但是如果你只是让线程终止并忘记它,那没关系:新创建的线程已经有一个有效的堆栈。
例如,让我们使用以下代码创建一个 C++ DLL:
我们将其命名为 CrashHandler.dll,并将其放在程序所在的目录中。然后在 C# 中,您可以像这样处理堆栈溢出:
笔记。非托管 DLL 和应用程序的目标体系结构必须匹配。对于 AnyCPU 应用程序,您需要为每个体系结构提供多个非托管 DLL,并根据应用程序的当前体系结构加载正确的 DLL。