C# 捕获堆栈溢出异常
Posted
技术标签:
【中文标题】C# 捕获堆栈溢出异常【英文标题】:C# catch a stack overflow exception 【发布时间】:2010-12-08 15:00:25 【问题描述】:我对引发堆栈溢出异常的方法进行了递归调用。第一次调用被 try catch 块包围,但没有捕获到异常。
堆栈溢出异常是否以特殊方式表现?我可以正确捕获/处理异常吗?
不确定是否相关,但有更多信息:
主线程中没有抛出异常
代码引发异常的对象由 Assembly.LoadFrom(...).CreateInstance(...) 手动加载
【问题讨论】:
@RichardOD,确定我修复了这个错误,因为它是一个错误。但是问题可能以不同的方式出现,我想处理它 同意,堆栈溢出是一个无法捕获的严重错误,因为它不应该被捕获。改为修复损坏的代码。 @RichardOD:如果有人想设计,例如一种递归下降解析器,并且不会对超出主机实际要求的深度施加人为限制,应该如何去做呢?如果我有我的 druthers,就会有一个 StackCritical 异常可以被显式捕获,它会在还有一点堆栈空间时被触发;它会禁用自己,直到它被实际抛出,然后直到安全数量的堆栈空间剩余时才能被捕获。 这个问题很有用——如果发生堆栈溢出异常,我希望单元测试失败——但 NUnit 只是将测试移动到“忽略”类别,而不是像其他测试那样失败例外——我需要抓住它并改用Assert.Fail
。这么严重——我们该怎么做?
【参考方案1】:
从 2.0 开始,只能在以下情况下捕获 *** 异常。
-
CLR 正在托管环境中运行*,其中主机专门允许处理 *** 异常
*** 异常是由用户代码引发的,而不是由于实际的堆栈溢出情况 (Reference)
*“托管环境”如“我的代码托管 CLR 并且我配置 CLR 的选项”而不是“我的代码在共享托管上运行”
【讨论】:
如果在任何相关场景中都无法捕捉到,为什么***Exception对象存在? @Manu 至少有几个原因。 1)它是否可以在 1.1 中被捕获,因此有目的。 2) 如果您托管 CLR,它仍然可以被捕获,因此它仍然是有效的异常类型 如果无法捕获...为什么解释发生情况的 windows 事件默认不包含完整的堆栈跟踪? 如何在托管环境中处理 ***Exceptions?我问的原因是因为我运行一个托管环境,并且我遇到了这个确切的问题,它破坏了整个应用程序池。我宁愿让它中止线程,在那里它可以展开回到顶部,然后我可以记录错误并继续,而不会杀死所有 apppool 的线程。Starting with 2.0 ...
,我很好奇,是什么阻止他们抓到 SO 以及它是如何可能的 1.1
(你在评论中提到过)?【参考方案2】:
正确的方法是修复溢出,但是....
你可以给自己一个更大的筹码:-
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();
只要抓住奶酪。 ;)
【讨论】:
Cheese
远非具体。我会去throw new CheeseException("Gouda");
@C.Evenhuis 虽然毫无疑问,高达是一种特殊的奶酪,但它应该是 RollingCheeseException("Double Gloucester") 真正看到 cheese-rolling.co.uk
大声笑,1) 修复是不可能的,因为如果不抓住它,你通常不知道它发生在哪里 2) 增加 Stacksize 对于 endless 递归和m 3) 检查是没有用的正确位置的堆栈就像第一个
但我有乳糖不耐症【参考方案3】:
来自***Exceptions 上的 MSDN 页面:
在 .NET 的早期版本中 框架,您的应用程序可以 捕获 ***Exception 对象 (例如,从 无限递归)。然而,那 目前不鼓励这种做法 因为重要的附加代码是 需要可靠地捕捉堆栈 溢出异常并继续 程序执行。
从 .NET 框架开始 2.0 版,一个 ***Exception 对象不能被 try-catch 捕获 块,对应的进程是 默认终止。所以, 建议用户编写他们的代码 检测和防止堆栈 溢出。例如,如果您的 应用依赖于递归,使用 计数器或状态条件 终止递归循环。笔记 一个应用程序托管 公共语言运行时 (CLR) 可以 指定 CLR 卸载 堆栈所在的应用程序域 发生溢出异常,让 相应的过程继续。为了 更多信息,请参阅 ICLRPolicyManager 接口和 托管公共语言运行时。
【讨论】:
【参考方案4】:正如一些用户已经说过的,您无法捕获异常。但是,如果您很难找出它发生的位置,您可能需要将 Visual Studio 配置为在它被抛出时中断。
为此,您需要从“调试”菜单中打开异常设置。在旧版本的 Visual Studio 中,这是在 'Debug' - 'Exceptions';在较新的版本中,它位于“调试”-“Windows”-“异常设置”。
打开设置后,展开“Common Language Runtime Exceptions”,展开“System”,向下滚动并选中“System.***Exception”。然后您可以查看调用堆栈并查找调用的重复模式。这应该让您知道在哪里查找导致堆栈溢出的代码。
【讨论】:
在哪里调试 - VS 2015 中的异常? 调试 - Windows - 异常设置【参考方案5】:如上所述,由于进程状态损坏,无法捕获系统引发的 ***Exception。但是有一种方法可以将异常视为事件:
http://msdn.microsoft.com/en-us/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/en-us/library/system.***exception.aspx
【讨论】:
从 CLR 2.0 开始,***Exception
默认终止进程。
没有。您可以捕获 OOM,在某些情况下这样做可能是有意义的。我不知道你说的线程消失是什么意思。如果线程有未处理的异常,CLR 将终止该进程。如果您的线程完成其方法,它将被清理。【参考方案7】:
你不能。 CLR 不会让你。堆栈溢出是一个致命错误,无法从中恢复。
【讨论】:
那么,如果不是可捕获的,而是使单元测试运行器崩溃,那么如何使单元测试因此异常而失败? @BrainSlugs83。你不知道,因为这是一个愚蠢的想法。你为什么要测试你的代码是否因 ***Exception 而失败?如果 CLR 发生变化以便处理更深的堆栈会发生什么?如果你在某个已经有深度嵌套堆栈的地方调用你的单元测试函数会发生什么?这似乎是无法测试的东西。如果您尝试手动抛出它,请为任务选择更好的异常。【参考方案8】:你不能因为大多数帖子都在解释,让我添加另一个区域:
在许多网站上,您会发现人们说避免这种情况的方法是使用不同的 AppDomain,因此如果发生这种情况,该域将被卸载。这是绝对错误的(除非您托管 CLR),因为 CLR 的默认行为会引发 KillProcess 事件,从而关闭您的默认 AppDomain。
【讨论】:
【参考方案9】:这是不可能的,并且有充分的理由(首先,想想所有那些 catch(Exception))。
如果您想在堆栈溢出后继续执行,请在不同的 AppDomain 中运行危险代码。可以设置 CLR 策略以在溢出时终止当前 AppDomain 而不会影响原始域。
【讨论】:
“catch”语句不会成为真正的问题,因为当 catch 语句可以执行时,系统将回滚任何试图使用两个堆栈空间的结果。没有理由捕获堆栈溢出异常是危险的。无法捕获此类异常的原因是,允许安全地捕获它们需要向使用堆栈的所有代码添加一些额外的开销,即使它没有溢出。 在某些时候,这种说法并没有经过深思熟虑。如果您无法捕捉 ***,您可能永远不知道它在生产环境中发生的位置。以上是关于C# 捕获堆栈溢出异常的主要内容,如果未能解决你的问题,请参考以下文章