哪些设计决策导致StackOverflowException在.NET 2.0+中无法捕获?

时间:2012-05-30 11:47:20

标签: .net clr stack-overflow

据我所知,StackOverflowException应该无法取消的基本原因。但它确实如此。

线程堆栈的最大大小默认为1MB或4MB,具体取决于位数,但此空间仅保留。它没有被提交(因此只使用虚拟地址空间),直到实际需要那么多堆栈。

据我所知,为什么它无法捕捉的基本思想是,到那时你已经耗尽所有堆栈,因此无法真正执行除代码之外的任何其他内容非常精心设计的堆栈外状况。但是这需要明显的解决方案:在之前抛出StackOverflowException 我们已经用完所有堆栈空间了!

为什么不这样做?我能想到的唯一一个远程合理的原因是,确定这将消耗的额外虚拟地址空间(不是真正的内存使用,请注意!)不值得做出此异常的好处开捕

我还没有考虑其他问题吗?

我觉得我必须要问,因为大多数答案在SO上解决这个问题似乎意味着不可能让它合理地运行起来, 这就是.NET让它们无法捕获的原因。


可捕获变体的确切实现细节似乎并不重要,但这只是一个想法。首先,将默认保留堆栈大小加倍,但将异常设置为在1MB使用时触发。到目前为止,这与现有方法完全相同。但是当达到阈值时,抛出一个可捕获的异常,而不是抛出一个无法捕获的异常,同时将阈值增加到1.5MB。如果异常被捕获并且我们再次超过限制,则将其设置为1.75MB。然后是1.875MB。等等。每个嵌套和处理的异常都会获得不断增加的额外堆栈空间,直到我们接近2MB才能要求抛出一个无法捕获的变量。

要在成功处理StackOverflowException之后减少阈值,让我们将内存页面标记为我们刚刚通过的堆栈大小的一半(因此在第一个实例中为0.5MB)写入错误。当我们回到堆栈使用水平时,故障处理程序将被完全触发,因此它并不昂贵。处理程序将检查实际的堆栈大小,并在适当的情况下将阈值降低。

1 个答案:

答案 0 :(得分:4)

我不知道为什么它从1.1改为2.0(希望Eric有时会发表评论),但我怀疑是因为这样的情况:

public bool Count(int64 i)
{
    try
    {
        // somewhere in here a stack overflow exception is thrown
        if(i < int64.MaxValue)
            return Count(i+1);
        else
            return true;
    } Catch(StackOverflowException soEx)
    {
        HandleError(soEx);
    }
}

public void HandleError(Exception ex)
{
    // error handling code here
}

你实际上会在HandleError(soEx);行中抛出一个异常,因为它在堆栈太满之前停止了。

我怀疑这就是他们选择使堆栈溢出异常无法捕获的原因,因为你无法捕获它然后调用该catch块中的任何方法。

至于你的建议,他们不希望有一个堆栈大小和有效的堆栈大小,因为这会使事情变得复杂,我怀疑他们也不希望有一个可变的堆栈大小。这些事情中的任何一个都会使框架复杂化并且只提供部分解决方案,为了争论,如果您的代码是:

public void HandleError(Exception ex)
{
    try
    {
        HandleError(soEx);
    } Catch(StackOverflowException soEx)
    {
        HandleError(soEx);
    }
}

使用变量堆栈并且无法实际阻止StackOverflowException被捕获,这会导致其他崩溃。

所以让它只是扔掉并且不可捕捉更容易,也更容易预测。

修改:This answer指出 仍能在CLR 2.0 +中捕获StackOverflowExceptions的唯一情况