我們有很多嵌套的異步方法,並查看我們不太瞭解的行爲。就拿這個簡單的C#控制檯應用程序StackOverflowExceptions堆棧展開時的嵌套異步方法
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在異常情況下發生得比在我們不拋出異常的時候早?
這可能是有用的https://stackoverflow.com/questions/13808166/recursion-and-the-await-async-keywords – Bit
謝謝@Bit,雖然它觸及相同的主題,它不回答我們的具體問題。 –
也相關,但我從來沒有給予回報的旅程很多考慮:https://stackoverflow.com/questions/35464468/async-recursion-where-is-my-memory-actually-going – spender