为什么下面的代码在.Net4.8中只有17深度递归会导致StackOverflowException?然而,这在 NetCore 3.1 中不会发生(我可以将计数设置为 10_000,它仍然有效)
class Program
{
static async Task Main(string[] args)
{
try
{
await TestAsync(17);
}
catch(Exception e)
{
Console.WriteLine("Exception caught: " + e);
}
}
static async Task TestAsync(int count)
{
await Task.Run(() =>
{
if (count <= 0)
throw new Exception("ex");
});
Console.WriteLine(count);
await TestAsync2(count);
}
static async Task TestAsync2(int count) => await TestAsync3(count);
static async Task TestAsync3(int count) => await TestAsync4(count);
static async Task TestAsync4(int count) => await TestAsync5(count);
static async Task TestAsync5(int count) => await TestAsync6(count);
static async Task TestAsync6(int count) => await TestAsync(count - 1);
}
这是 .Net 4.8 中的已知错误吗?我会在这样的函数中排除超过 17 级的递归...这是否实际上意味着不建议使用 async/await 编写递归?
更新:简化版
class Program
{
// needs to be compiled as AnyCpu Prefer 64-bit
static async Task Main(string[] args)
{
try
{
await TestAsync(97); // 96 still works
}
catch(Exception e)
{
Console.WriteLine("Exception caught: " + e);
}
}
static async Task TestAsync(int count)
{
await Task.Run(() =>
{
if (count <= 0)
throw new Exception("ex");
});
Console.WriteLine(count);
await TestAsync(count-1);
}
}
只有在选择的时候它才会发生得那么快Any Cpu与首选32 位禁用,但可以在多个 .net 版本(.Net 4.7.2 和 .Net 4.8)上的多台计算机(Windows 1903 和 1909)上重现
我怀疑您正在看到 Stack Overflow完成情况- 即,每个数字都会一直打印到1
在堆栈溢出消息之前。
我的猜测是这种行为是因为await使用同步延续 https://blog.stephencleary.com/2012/12/dont-block-in-asynchronous-code.html。有supposed是防止同步延续溢出堆栈的代码 https://devblogs.microsoft.com/pfxteam/when-executesynchronously-doesnt-execute-synchronously/,但它是启发式的,并不总是有效。
我怀疑这种行为不会发生在 .NET Core 上,因为大量的优化工作已经投入到 .NET Core 中async
支持,可能意味着该平台上的延续占用更少的堆栈空间,从而使启发式检查起作用。启发式本身也可能已在 .NET Core 中修复。无论哪种方式,我都不会屏息期待 .NET Framework 获得这些更新。
我会在这样的函数中排除超过 17 级的递归......
不是真的 17. 你有 102 级递归(17 * 6
)。测量actual占用的堆栈空间是17 * 6 * (number of stacks to resume continuations)
。在我的机器上,17 个有效;它在超过 200 个地方失败(1200 个深度调用)。
请记住,这只发生在长序列中尾递归异步函数 - 即,它们都没有任何异步工作要做after their await
。如果您更改任何函数以进行其他异步工作after他们的递归await
,这将避免堆栈溢出:
static async Task TestAsync(int count)
{
await Task.Run(() =>
{
if (count <= 0)
throw new Exception("ex");
});
Console.WriteLine(count);
try
{
await TestAsync2(count);
}
finally
{
await Task.Yield(); // some other async work
}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)