本文介绍了.NET 4.8中的Async Await递归导致StackoverflowException(.Net Core 3.1中不存在!)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么下面的代码仅在17深度的递归下会导致.Net4.8中的StackOverflowException?但是,在NetCore 3.1中不会发生这种情况(我可以将计数设置为10_000,但仍然可以使用)

Why does the following code cause a StackOverflowException in .Net4.8 with only a 17-depth recursion? However this does not happen in NetCore 3.1 (I can set the count to 10_000 and it still works)

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编写递归?

Is this a known bug in .Net 4.8? I would except a lot more than 17 levels of recursion in such a function... Does this effectively mean writing recursions with async/await is not recommended?

更新:简体版本

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);
  }
}

只有在选择禁用32位的情况下选择任何CPU 时,这种情况才会发生得如此之快,但是在多个.net版本的多台计算机(Windows 1903和1909)上都可以重现. (.Net 4.7.2和.Net 4.8)

It only happens so fast when choosing Any Cpu with Prefer 32-bit disabled, but is reproducable on multiple machines (Windows 1903 and 1909) on multiple .net versions (.Net 4.7.2 and .Net 4.8)

推荐答案

我怀疑您在完成上看到堆栈溢出-即,每个数字都一直打印到1在堆栈溢出"消息之前.

I suspect you're seeing the Stack Overflow on the completions - i.e., every number is printed out all the way down to 1 before the Stack Overflow message.

我的猜测是,这种行为是因为 await使用同步延续.有 假定是阻止同步的代码堆栈溢出产生的连续性,但是它是一种启发式方法,并不总是有效.

My guess is that this behavior is because await uses synchronous continuations. There's supposed to be code that prevents synchronous continuations from overflowing the stack, but it's heuristic and doesn't always work.

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

I suspect this behavior doesn't happen on .NET Core because a lot of optimization work has gone into .NET Core's async support, likely meaning that continuations on that platform take up less stack space, making the heuristic check work. It is also possible that the heuristic itself was fixed in .NET Core. Either way, I wouldn't hold my breath expecting .NET Framework to get those updates.

不是真的17.您有102个级别的递归(17 * 6).要测量实际使用的 堆栈空间,将为17 * 6 * (number of stacks to resume continuations).在我的机器上,有17幅作品;它在200多个位置失败(深度为1200个调用).

Not really 17. You've got 102 levels of recursion (17 * 6). To measure the actual stack space taken up, it would be 17 * 6 * (number of stacks to resume continuations). On my machine, 17 works; it fails somewhere over 200 (1200 calls deep).

请记住,这仅在长序列的 tail递归异步函数中发生-即,在它们的await之后,它们都没有更多的异步工作要做 .如果将任何函数更改为在它们的递归await之后 后具有其他一些异步工作,则可以避免堆栈溢出:

Bear in mind that this only happens for long sequences of tail recursive asynchronous functions - i.e., none of them have any more asynchronous work to do after their await. If you change any of the functions to have some other asynchronous work after their recursive await, that will avoid the stack overflow:

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
  }
}

这篇关于.NET 4.8中的Async Await递归导致StackoverflowException(.Net Core 3.1中不存在!)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-31 02:03