使用 Async/Await 的无限递归调用从不抛出异常
Infinite Recursive calls with Async/Await never throws exception
我正在编写一个小型控制台应用程序以尝试熟悉使用 async/await。在这个应用程序中,我不小心创建了一个无限递归循环(我现在已经修复了)。不过,这个无限递归循环的行为让我感到惊讶。它没有抛出 WhosebugException
,而是陷入僵局。
考虑以下示例。如果在 runAsync
设置为 false
的情况下调用 Foo()
,它会抛出一个 WhosebugException
。但是当 runAsync
是 true
时,它就会陷入僵局(或者至少看起来是这样)。谁能解释为什么行为如此不同?
bool runAsync;
void Foo()
{
Task.WaitAll(Bar(),Bar());
}
async Task Bar()
{
if (runAsync)
await Task.Run(Foo).ConfigureAwait(false);
else
Foo();
}
不是真的死锁了。这会很快耗尽线程池中的可用线程。然后,每 500 毫秒注入一个新线程。你可以观察到,当你在那里输入一些 Console.WriteLine
日志时。
基本上,这段代码是无效的,因为它淹没了线程池。任何具有这种精神的东西都不能投入生产。
如果您让所有等待异步而不是使用 Task.WaitAll
,您会将明显的死锁变成失控的内存泄漏。这对您来说可能是一个有趣的实验。
异步版本不会死锁(如用户解释的那样)但不会抛出 WhosebugException
因为它不依赖于堆栈。
栈是为一个线程保留的内存区域(不像堆是所有线程共享的)。
当您调用异步方法时,它会同步运行(即使用相同的线程和堆栈),直到它到达未完成任务的等待状态。那时,该方法的其余部分被安排为延续,线程被释放(连同其堆栈)。
因此,当您使用 Task.Run
时,您正在将 Foo
卸载到另一个具有干净堆栈的 ThreadPool
线程,因此您永远不会获得 WhosebugException
。
但是,您可能会达到 OutOfMemoryException
,因为异步方法的状态机存储在堆中,可供所有线程恢复。这个例子会很快抛出,因为你没有耗尽 ThreadPool
:
static void Main()
{
Foo().Wait();
}
static async Task Foo()
{
await Task.Yield();
await Foo();
}
我正在编写一个小型控制台应用程序以尝试熟悉使用 async/await。在这个应用程序中,我不小心创建了一个无限递归循环(我现在已经修复了)。不过,这个无限递归循环的行为让我感到惊讶。它没有抛出 WhosebugException
,而是陷入僵局。
考虑以下示例。如果在 runAsync
设置为 false
的情况下调用 Foo()
,它会抛出一个 WhosebugException
。但是当 runAsync
是 true
时,它就会陷入僵局(或者至少看起来是这样)。谁能解释为什么行为如此不同?
bool runAsync;
void Foo()
{
Task.WaitAll(Bar(),Bar());
}
async Task Bar()
{
if (runAsync)
await Task.Run(Foo).ConfigureAwait(false);
else
Foo();
}
不是真的死锁了。这会很快耗尽线程池中的可用线程。然后,每 500 毫秒注入一个新线程。你可以观察到,当你在那里输入一些 Console.WriteLine
日志时。
基本上,这段代码是无效的,因为它淹没了线程池。任何具有这种精神的东西都不能投入生产。
如果您让所有等待异步而不是使用 Task.WaitAll
,您会将明显的死锁变成失控的内存泄漏。这对您来说可能是一个有趣的实验。
异步版本不会死锁(如用户解释的那样)但不会抛出 WhosebugException
因为它不依赖于堆栈。
栈是为一个线程保留的内存区域(不像堆是所有线程共享的)。
当您调用异步方法时,它会同步运行(即使用相同的线程和堆栈),直到它到达未完成任务的等待状态。那时,该方法的其余部分被安排为延续,线程被释放(连同其堆栈)。
因此,当您使用 Task.Run
时,您正在将 Foo
卸载到另一个具有干净堆栈的 ThreadPool
线程,因此您永远不会获得 WhosebugException
。
但是,您可能会达到 OutOfMemoryException
,因为异步方法的状态机存储在堆中,可供所有线程恢复。这个例子会很快抛出,因为你没有耗尽 ThreadPool
:
static void Main()
{
Foo().Wait();
}
static async Task Foo()
{
await Task.Yield();
await Foo();
}