C#捕获堆栈溢出异常


115

我有一个递归调用一个引发堆栈溢出异常的方法。第一次调用被try catch块包围,但未捕获异常。

堆栈溢出异常是否以特殊方式表现?我可以正确捕获/处理异常吗?

不确定是否相关,但还有其他信息:

  • 在主线程中没有抛出异常

  • 代码引发异常的对象由Assembly.LoadFrom(...)。CreateInstance(...)手动加载


3
@RichardOD,确保我已修复该错误,因为它是一个错误。但是,问题可能会以不同的方式出现,我想解决这个问题
Toto,

7
同意,堆栈溢出是一个严重的错误,由于不应该捕获而无法捕获。改正损坏的代码。
伊恩·肯普

11
@RichardOD:如果要设计例如递归下降解析器,并且不对超出主机实际要求的深度施加人为的限制,那该怎么办呢?如果我有德鲁特,将有一个StackCritical异常可以被明确地捕获,并在剩下少量堆栈空间时触发该异常。它会自行禁用,直到被实际抛出为止,然后,直到剩余安全数量的堆栈空间时,才能将其捕获。
超级猫

2
这个问题很有用-如果发生堆栈溢出异常,我想使单元测试失败-但NUnit只是将测试移到“忽略的”类别,而不是像其他异常一样使测试失败-我需要抓住它并Assert.Fail改为。如此认真-我们该如何处理?
BrainSlugs83 2014年

Answers:


109

从2.0版本开始,只能在以下情况下捕获StackOverflow异常。

  1. CLR在托管环境中运行*主机专门允许处理StackOverflow异常
  2. 用户代码抛出了stackoverflow异常,而不是由于实际的堆栈溢出情况(参考

* “托管环境”,如“我的代码托管CLR,并且我配置CLR的选项”中那样,而不是“我的代码在共享托管上运行”中


27
如果无法在任何相关的scebario中捕获它,为什么存在StackoverflowException对象?
Manu

9
@Manu至少有两个原因。1)是否可以在1.1中被捕获并因此具有目的。2)如果您托管CLR,仍然可以捕获它,因此它仍然是有效的异常类型
JaredPar 2009年

3
如果无法捕获...默认情况下,为什么解释事件的Windows事件为什么不包括完整堆栈跟踪?

10
如何允许在托管环境中处理StackOverflowException?我问的原因是因为我运行的是托管环境,而我遇到了这个确切的问题,它破坏了整个应用程序池。我更希望它中止线程,在那里它可以退回到顶部,然后我可以记录错误并继续进行而不会杀死所有apppool的线程。
Brain2000

Starting with 2.0 ...,我是老客户,是什么阻止了他们捕获SO,又有什么可能的方式1.1(您在评论中提到了这一点)?
M.kazem Akhgary

47

正确的方法是解决溢出问题,但是...

您可以给自己更大的筹码:-

using System.Threading;
Thread T = new Thread(threadDelegate, stackSizeInBytes);
T.Start();

您可以使用System.Diagnostics.StackTrace FrameCount属性对已使用的帧进行计数,并在达到帧数限制时抛出自己的异常。

或者,您可以计算剩余堆栈的大小,并在低于阈值时引发自己的异常:-

class Program
{
    static int n;
    static int topOfStack;
    const int stackSize = 1000000; // Default?

    // The func is 76 bytes, but we need space to unwind the exception.
    const int spaceRequired = 18*1024; 

    unsafe static void Main(string[] args)
    {
        int var;
        topOfStack = (int)&var;

        n=0;
        recurse();
    }

    unsafe static void recurse()
    {
        int remaining;
        remaining = stackSize - (topOfStack - (int)&remaining);
        if (remaining < spaceRequired)
            throw new Exception("Cheese");
        n++;
        recurse();
    }
}

赶上奶酪。;)


47
Cheese还远远不够。我会去throw new CheeseException("Gouda");
C.Evenhuis

13
@ C.Evenhuis毫无疑问,豪达干酪是一种出色的奶酪,它应该是RollingCheeseException(“ Double Gloucester”)真正看到的是cheese-rolling.co.uk

3
笑,1)固定是不可能的,因为没有抓住它,你往往不知道它发生在哪里2)增加堆栈大小是无用无尽的递归和m 3)检查堆栈在正确的位置是像第
FIRO

2
但我不耐乳糖
redoc '18

39

StackOverflowException的MSDN页面上:

在.NET Framework的早期版本中,您的应用程序可能会捕获StackOverflowException对象(例如,从无限制的递归中恢复)。但是,目前不建议采用这种做法,因为需要大量额外的代码才能可靠地捕获堆栈溢出异常并继续执行程序。

从.NET Framework 2.0版开始,try-catch块无法捕获StackOverflowException对象,并且默认情况下终止了相应的进程。因此,建议用户编写其代码以检测并防止堆栈溢出。例如,如果您的应用程序依赖于递归,请使用计数器或状态条件终止递归循环。请注意,承载公共语言运行时(CLR)的应用程序可以指定CLR卸载发生堆栈溢出异常的应用程序域,并继续进行相应的处理。有关更多信息,请参见ICLRPolicyManager界面和托管公共语言运行时。


23

正如几个用户已经说过的那样,您无法捕获异常。但是,如果您想弄清楚它在哪里发生,则可能需要配置Visual Studio,使其在抛出时中断。

为此,您需要从“调试”菜单中打开“例外设置”。在旧版本的Visual Studio中,此位置为“调试”-“异常”;在较新版本中,它位于“调试”-“ Windows”-“异常设置”中。

打开设置后,展开“公共语言运行时异常”,展开“系统”,向下滚动并检查“ System.StackOverflowException”。然后,您可以查看调用堆栈并查找重复的调用模式。那应该给你一个思路,去寻找导致堆栈溢出的代码。


1
VS 2015中的“调试-异常”在哪里?
FrenkyB

1
调试-Windows-异常设置
西蒙(Simon)

15

如上所述,由于进程状态已损坏,无法捕获由系统引发的StackOverflowException。但是有一种方法可以将异常视为事件:

http://msdn.microsoft.com/zh-CN/library/system.appdomain.unhandledexception.aspx

从.NET Framework版本4开始,除非破坏事件处理程序的安全性至关重要并且具有HandleProcessCorruptedStateExceptionsAttribute属性,否则不会引发破坏进程状态的异常(例如堆栈溢出或访问冲突)的事件。

不过,您的应用程序将在退出事件功能后终止(一个非常肮脏的解决方法,是在此事件中重新启动应用程序,哈哈,从未这样做,也永远不会这样做)。但这足以记录日志!

在.NET Framework版本1.0和1.1中,运行时会捕获在主应用程序线程以外的线程中发生的未处理异常,因此不会导致应用程序终止。因此,有可能在不终止应用程序的情况下引发UnhandledException事件。从.NET Framework 2.0版开始,删除了针对子线程中未处理异常的支持,因为此类静默故障的累积影响包括性能下降,数据损坏和锁定,所有这些都难以调试。有关更多信息,包括运行时未终止的情况的列表,请参阅托管线程中的异常。


6

是CLR 2.0堆栈溢出被认为是不可恢复的情况。因此,运行时仍会关闭进程。

有关详细信息,请参阅文档http://msdn.microsoft.com/zh-cn/library/system.stackoverflowexception.aspx


在CLR 2.0 StackOverflowException中,默认情况下会终止该过程。
Brian Rasmussen

不可以。您可以捕获OOM,在某些情况下,这样做很有意义。我不知道线程消失的意思。如果线程具有未处理的异常,则CLR将终止该进程。如果您的线程完成其方法,它将被清除。
Brian Rasmussen 2015年

5

你不能 CLR不允许您这样做。堆栈溢出是一个致命错误,无法从中恢复。


那么,如何使该单元测试因该异常而不是可捕获的崩溃而使该单元测试运行程序崩溃,导致该异常失败?
BrainSlugs83 2014年

1
@ BrainSlugs83。您不会,因为那是一个愚蠢的主意。为什么要测试代码是否由于StackOverflowException而失败?如果CLR发生更改,以便可以处理更深的堆栈,该怎么办?如果您在已经具有深层嵌套堆栈的位置调用单元测试函数,会发生什么情况?似乎无法测试。如果您尝试手动将其抛出,请为任务选择一个更好的例外。
马修·沙利

5

您不能像大多数帖子所解释的那样,让我添加另一个区域:

在许多网站上,您会发现有人说避免这种情况的方法是使用其他AppDomain,因此,如果发生这种情况,该域将被卸载。这是绝对错误的(除非您托管CLR),因为CLR的默认行为将引发KillProcess事件,从而降低默认的AppDomain。


3

这是不可能的,并且有充分的理由(其中一个原因,请考虑一下所有这些catch(Exception){})。

如果要在堆栈溢出后继续执行,请在其他AppDomain中运行危险代码。可以将CLR策略设置为在溢出时终止当前AppDomain,而不会影响原始域。


2
“ catch”语句并不是真正的问题,因为到了catch语句可以执行的时候,系统将回滚尝试使用两个多堆栈空间的结果。没有理由捕获堆栈溢出异常将很危险。之所以无法捕获此类异常,是因为安全地捕获它们将需要向使用该堆栈的所有代码添加一些额外的开销,即使它不会溢出也是如此。
supercat 2013年

4
在某些时候,这种说法还没有被深思熟虑。如果您无法捕获Stackoverflow,那么您可能永远不知道它在生产环境中发生的位置。
Offler
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.