一个非常简单的示例应用程序(.NET 4.6.2)在 12737 的递归深度处产生StackOverflowException,如果是 10243 ,则递减到递归深度 10243 大多数内部函数调用抛出一个异常,这是预期的和OK。
如果我使用Lazy<T>
来短暂保存中间结果,则如果没有抛出异常且递归深度 2207 > 105 ,如果抛出异常。
注意:如果编译为x64,则深度为 105 的StackOverflowException只能被观察到。使用x86(32位)时,效果首先出现在 4272 的深度。单声道(就像在https://repl.it处使用的那样)在 74200 的深度下可以毫无问题地工作。
StackOverflowException不会在深度递归中发生,而是在升级回主程序时发生。最后一个块被深度处理,然后程序就死了:
Exception System.InvalidOperationException at 105
Finally at 105
...
Exception System.InvalidOperationException at 55
Finally at 55
Exception System.InvalidOperationException at 54
Finally at 54
Process is terminated due to StackOverflowException.
或在调试器中:
The program '[xxxxx] Test.vshost.exe' has exited with code -2147023895 (0x800703e9).
谁可以解释一下?
public class Program
{
private class Test
{
private int maxDepth;
private int CalculateWithLazy(int depth)
{
try
{
var lazy = new Lazy<int>(() => this.Calculate(depth));
return lazy.Value;
}
catch (Exception e)
{
Console.WriteLine("Exception " + e.GetType() + " at " + depth);
throw;
}
finally
{
Console.WriteLine("Finally at " + depth);
}
}
private int Calculate(int depth)
{
if (depth >= this.maxDepth) throw new InvalidOperationException("Max. recursion depth reached.");
return this.CalculateWithLazy(depth + 1);
}
public void Run()
{
for (int i = 1; i < 100000; i++)
{
this.maxDepth = i;
try
{
Console.WriteLine("MaxDepth: " + i);
this.CalculateWithLazy(0);
}
catch { /* ignore */ }
}
}
}
public static void Main(string[] args)
{
var test = new Test();
test.Run();
Console.Read();
}
更新:只需在递归方法中使用try-catch-throw块,就可以在不使用Lazy<T>
的情况下重现问题。
[MethodImpl(MethodImplOptions.NoInlining)]
private int Calculate(int depth)
{
try
{
if (depth >= this.maxDepth) throw new InvalidOperationException("Max. recursion depth reached.");
return this.Calculate2(depth + 1);
}
catch
{
throw;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int Calculate2(int depth) // just to prevent the compiler from tail-recursion-optimization
{
return this.Calculate(depth);
}
public void Run()
{
for (int i = 1; i < 100000; i++)
{
this.maxDepth = i;
try
{
Console.WriteLine("MaxDepth: " + i);
this.Calculate(0);
}
catch(Exception e)
{
Console.WriteLine("Finished with " + e.GetType());
}
}
}
答案 0 :(得分:1)
有两个原因:
请阅读以下内容以了解更多详情:
分配给调用堆栈的内存的数量是固定的(这是Thread的 maxStackSize )
因此,适合固定内存量的堆栈帧的数量取决于这些堆栈帧的大小。
如果在方法中使用其他变量,则必须将它们写入堆栈,并占用内存。
此外,如果您使用Lazy<T>
,堆栈帧的数量会有所不同,因为它包含一个需要再调用一次的委托(还有一个您不计算的堆栈帧)
这正是您遇到的情况,如果您在lazy
内使用额外的CalculateWithLazy
变量,您的堆栈帧只占用更多空间,这就是为什么您在程序失败之前获得更少的堆栈帧的原因StackOverflowException
可以更精确地计算出来,但我认为这种近似解释足以理解不同行为的原因。
以下是如何找出线程的 maxStackSize 的方法: How to find current thread's max stack size in .net?
以下是如何找出引用类型变量的大小(取决于平台+一些开销):How much memory does a C# reference consume?
最后,您的代码中只有System.Int32
,因此需要32个字节的内存
如果你有任何自定义结构(值类型)计算它们的大小将是一个相当大的挑战,请参阅@Hans Passant在这个问题中的答案:
How do I check the number of bytes consumed by a structure?
答案 1 :(得分:0)
通过使用Lazy,您将添加更多调用:对Value
属性的调用,可能是代理上的Invoke
,可能更多取决于它是如何实现的。您的调试器调用堆栈可以帮助您查看正在进行的操作。