【发布时间】:2017-06-26 13:05:13
【问题描述】:
我们有很多嵌套的异步方法,并且看到了我们并不真正理解的行为。以这个简单的 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() 来解决这个问题,但是我们有几个问题:
- 为什么堆栈会在展开路径上增长(与 在等待时不会导致线程切换的方法)?
- 为什么 StackOverflowException 在 Exception 情况下比我们不抛出异常时更早出现?
【问题讨论】:
-
感谢@Bit,虽然它涉及相同的主题,但它没有回答我们的具体问题。
-
也有关系,但我从来没有考虑过回程:stackoverflow.com/questions/35464468/…
-
我运行它并得到了和你一样的结果。当我删除对
await Task.Yield()的调用时,它会一直展开堆栈直到开始。然后我尝试通过将第一个参数设为 3000 来增加迭代次数,这在e 1568 of 3000 (on threadId: 5)上引发了相同的异常
标签: c# .net async-await stack-overflow