Mic*_*eem 15 .net c# stack-overflow async-await
我们有很多嵌套的异步方法,看到我们并不真正理解的行为.以这个简单的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";
}
}
}
Run Code Online (Sandbox Code Playgroud)
当我们使用以下参数运行此示例时:
AsyncStackSample.exe 2000 false
Run Code Online (Sandbox Code Playgroud)
我们得到一个StackOverflowException,这是我们在控制台中看到的最后一条消息:
e 331 of 2000 (on threadId: 4)
Run Code Online (Sandbox Code Playgroud)
当我们将参数更改为
AsyncStackSample.exe 2000 true
Run Code Online (Sandbox Code Playgroud)
我们以此消息结束
e 831 of 2000 (on threadId: 4)
Run Code Online (Sandbox Code Playgroud)
因此StackOverflowException在堆栈的展开时发生(不确定我们是否应该调用它,但是在我们的示例中的递归调用之后发生StackOverflowException,在同步代码中,StackOverflowException将始终在嵌套方法调用上发生).在我们抛出异常的情况下,StackOverflowException甚至更早发生.
我们知道我们可以通过调用finally块中的Task.Yield()来解决这个问题,但我们有几个问题:
Ste*_*ary 13
为什么堆栈在展开路径上增长(与不会导致await上的线程切换的方法相比)?
核心原因是因为await使用TaskContinuationOptions.ExecuteSynchronously标志安排其延续.
因此,当执行"最内层"时Yield,你最终得到的是3000个未完成的任务,每个"内部"任务都有一个完成回调,完成最后一个内部任务.这一切都在堆中.
当最内层Yield恢复时(在线程池线程上),continuation(同步)执行Test方法的其余部分,完成其任务,(同步)执行Test方法的其余部分,完成其任务等等,一些一千次.因此,该线程池线程上的调用堆栈实际上随着每个任务的完成而增长.
就个人而言,我发现这种行为令人惊讶并将其报告为一个错误.但是,该漏洞被微软称为"按设计".值得注意的是,JavaScript中的Promises规范(以及扩展中的行为await)始终具有承诺完成异步运行且从不同步.这让一些JS开发者感到困惑,但这是我所期望的行为.
通常情况下,它可以正常运行,并且ExecuteSynchronously可以作为次要的性能改进.但正如您所指出的,有些情况可能会导致"异步递归" StackOverflowException.
目前有一些在BCL异步运行延续堆栈是太满启发,但他们只是试探,并不总是工作.
为什么StackOverflowException在Exception情况下比在不抛出异常时更早出现?
这是一个很好的问题.我不知道.:)