c# - .NET 4.8 中的异步等待递归导致 StackoverflowException(不在 .Net Core 3.1 中!)

标签 c# recursion async-await

为什么下面的代码在 .Net4.8 中导致 StackOverflowException 只有 17 深度递归?但是这在 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);
  }
}

只有在选择 时才会发生这么快任意 CPU 与首选 32 位禁用 ,但可以在多个 .net 版本(.Net 4.7.2 和 .Net 4.8)的多台机器(Windows 1903 和 1909)上重现

最佳答案

我怀疑您在完成时看到了堆栈溢出 - 即,每个数字一直打印到 1在 Stack Overflow 消息之前。

我的猜测是这种行为是因为 await uses synchronous continuations .有supposed to be code that prevents synchronous continuations from overflowing the stack ,但它是启发式的,并不总是有效。

我怀疑这种行为不会发生在 .NET Core 上,因为大量优化工作已经进入 .NET Core 的 async支持,可能意味着该平台上的延续占用更少的堆栈空间,使启发式检查工作。启发式本身也可能在 .NET Core 中被修复。无论哪种方式,我都不会屏住呼吸期待 .NET Framework 获得这些更新。

I would except a lot more than 17 levels of recursion in such a function...



不是真的 17。你有 102 级递归( 17 * 6 )。要测量实际占用的堆栈空间,它将是 17 * 6 * (number of stacks to resume continuations) .在我的机器上,有 17 个作品;它在 200 多个地方失败(1200 次深度调用)。

请记住,这只发生在尾递归异步函数的长序列中——也就是说,在它们的 await 之后,没有任何异步工作要做。 .如果您将任何函数更改为在递归后进行其他异步工作 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
  }
}

关于c# - .NET 4.8 中的异步等待递归导致 StackoverflowException(不在 .Net Core 3.1 中!),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61103236/

相关文章:

c# - LINQ 从列表中的列表中获取唯一项

c++ - 递归阶乘函数无法正常工作

c# - 触发并忘记异步任务方法有时不会调用

javascript - 将递归 .then 更改为 async/await

node.js - 嵌套多个异步等待

c# - 当应用程序处于焦点状态时,如何捕获 WinForms 应用程序上的键?

c# - UWP InkCanvas 内存不足

c# - 如何使用 RichTextBox 控件将\line 添加到 RTF

c++ - C++段错误中的堆算法

c - 搜索二叉树然后更新重复计数