代码之家  ›  专栏  ›  技术社区  ›  Michiel Overeem

堆栈展开时嵌套异步方法中的StackOverflowException

  •  19
  • Michiel Overeem  · 技术社区  · 8 年前

    我们有很多嵌套的异步方法,并且看到了我们并不真正理解的行为。以这个简单的C控制台应用程序为例

    public class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var x = Test(index: 0, max: int.Parse(args[0]), throwException: bool.Parse(args[1])).GetAwaiter().GetResult();
    
                Console.WriteLine(x);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex);
            }
            
            Console.ReadKey();
        }
        
        static async Task<string> Test(int index, int max, bool throwException)
        {
            await Task.Yield();
        
            if(index < max)
            {
                var nextIndex = index + 1;
                try
                {
                    Console.WriteLine($"b {nextIndex} of {max} (on threadId: {Thread.CurrentThread.ManagedThreadId})");
                  
                    return await Test(nextIndex, max, throwException).ConfigureAwait(false);
                }
                finally
                {
                    Console.WriteLine($"e {nextIndex} of {max} (on threadId: {Thread.CurrentThread.ManagedThreadId})");
                }
            }
        
            if(throwException)
            {
                throw new Exception("");
            }
        
            return "hello";
        }
    }
    

    AsyncStackSample。exe 2000错误

    StackOverflowException 这是我们在控制台中看到的最后一条消息:

    当我们把论点变成

    AsyncStackSample。exe 2000 true

    e 831/2000(在threadId:4上)

    栈溢出异常 在堆栈展开时发生(不确定是否应该这样称呼,但 在我们的示例中,在同步代码中,在递归调用之后发生 栈溢出异常 栈溢出异常 甚至更早发生。

    Task.Yield() 在最后一块中,我们有几个问题:

    1. 方法,该方法不会导致等待上的线程切换)?
    2. 栈溢出异常 在异常情况下比不引发异常时更早发生?
    1 回复  |  直到 3 年前
        1
  •  16
  •   Stephen Cleary    8 年前

    为什么堆栈会在展开路径上增长(与不会导致等待上的线程切换的方法相比)?

    核心原因是 await schedules its continuations with the TaskContinuationOptions.ExecuteSynchronously flag

    所以,当“最里面的” Yield 执行时,您最终得到的是3000个未完成的任务,每个“内部”任务持有一个完成回调,该回调完成了下一个最新的内部任务。这些都在堆里。

    产量 继续(在线程池线程上),继续(同步)执行 Test 方法,该方法完成其任务,并(同步)执行 测验 它的 增长的 随着每个任务的完成。

    就我个人而言,我发现这种行为令人惊讶,并将其报告为一个bug。然而,微软关闭了这个漏洞,称之为“故意的”。有趣的是,JavaScript中的Promises规范(以及扩展后的 总是 promise完成是否异步运行,以及 同步。这让一些JS开发人员感到困惑,但这是我所期望的行为。

    通常情况下,一切正常 ExecuteSynchronously 作为轻微的性能改进。但正如您所注意到的,在一些场景中,如“异步递归”,它可能会导致 StackOverflowException .

    一些 heuristics in the BCL to run continuations asynchronously if the stack is too full

    为什么StackOverflowException在异常情况下比我们不抛出异常时发生得更早?

    这是一个很好的问题。我不知道。:)

    推荐文章