StackOverflowExceptions在堆栈展开时的嵌套异步方法中
我们有很多嵌套的异步方法,并查看我们不太了解的行为。 以例如这个简单的C#控制台应用程序为例
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncStackSample
{
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 false
我们得到一个StackOverflowException,这是我们在控制台中看到的最后一条消息:
e 331 of 2000 (on threadId: 4)
当我们改变参数
AsyncStackSample.exe 2000 true
我们以此消息结束
e 831 of 2000 (on threadId: 4)
所以StackOverflowException发生在栈的展开(不确定是否应该调用它,但StackOverflowException发生在我们的示例中的递归调用之后,在同步代码中,StackOverflowException总会在嵌套方法调用中发生)。 在我们抛出异常的情况下,StackOverflowException甚至更早发生。
我们知道我们可以通过在finally块中调用Task.Yield()来解决这个问题,但我们有几个问题:
为什么堆栈在展开路径上增长(与不引起await线程切换的方法相比)?
核心原因是因为await
使用TaskContinuationOptions.ExecuteSynchronously
标志来调度其延续。
因此,当执行“最内层” Yield
,最终会产生3000个不完整的任务,每个“内部”任务持有完成回调,完成下一个内部任务。 这一切都在堆。
当最内层Yield
恢复时(在线程池线程上),continuation(同步)执行Test
方法的剩余部分,完成其任务,同步执行Test
方法的剩余部分,完成其任务等。 ,几千次。 所以,每个任务完成后,该线程池线程上的调用堆栈实际上正在增长。
就我个人而言,我觉得这种行为令人惊讶,并将其报告为一个错误。 但是,这个错误被微软封为“按设计”。 有趣的是,JavaScript中的Promises规范(以及扩展, await
的行为)总是有承诺完成异步运行并且从不同步运行。 这让一些JS开发人员感到困惑,但这是我期望的行为。
通常情况下,它可以正常工作,并且ExecuteSynchronously
作为次要的性能改进。 但是正如你所说的那样,有些情况像“异步递归”可能导致StackOverflowException
。
如果堆栈太满,BCL中有一些启发式异步运行延续,但它们只是启发式,并不总是有效。
为什么StackOverflowException在Exception的情况下比在我们不抛出异常的时候更早出现?
这是一个很好的问题。 我不知道。 :)
链接地址: http://www.djcxy.com/p/80767.html上一篇: StackOverflowExceptions in nested async methods on unwinding of the stack
下一篇: ShimsContext.Create is throwing a StackOverflowException