我可以递归调用异步函数而不会溢出堆栈吗?

Rol*_*lie 5 c# asynchronous

由于异步函数的返回站点不是调用者,我认为这是有效的,但我认为为了以防万一我会验证这是安全的.如果不是,为什么会溢出堆栈?

static async Task CheckAsync(TimeSpan recursiveTimer)
{
    // do some work

    await Task.Delay(recursiveTimer);
    CheckAsync(recursiveTimer);
}
Run Code Online (Sandbox Code Playgroud)

编辑:我决定尝试一下 - 看起来它不会溢出堆栈(它现在在我的机器上运行 - 它当前正在调用210,000).我假设的原因是因为CheckAsync函数的返回站点实际上不是CheckAsync,而是异步管道中的某个地方.因此,当CheckAsync调用CheckAsync时,它实际上并没有通过普通函数调用机制添加到调用堆栈,而是将该函数作为对象放在某个异步"待执行"队列上,该队列通过管理异步函数的其他线程运行.

对于那些了解这种机制的人来说:这听起来是对的吗?

Jon*_*eet 5

它为你工作的原因不是因为CheckAsync被调用的方式,而是因为你在等待结果Task.Delay.这将始终返回"尚未完成"的任务,因此等待它将安排继续.该延续将在有效的空堆栈上触发,因此您进行递归调用并不重要.

现在,我认为你仍然有效地存在内存泄漏,因为IIRC框架将跟踪一个"逻辑堆栈",它将变得越来越大......但是它将被存储在堆上并扩展直到你用完为止记忆.

如果你想看到堆栈爆炸,你需要做的就是将代码更改为:

static async Task CheckAsync(TimeSpan recursiveTimer)
{
    // Whatever
    await Task.FromResult(5);
    CheckAsync(recursiveTimer);
}
Run Code Online (Sandbox Code Playgroud)

在这一点上,假设"无论什么"中的代码没有等待任何东西,你将拥有完全同步的代码,只是Task用于跟踪完成和异常.

我当然不会推荐这作为重复工作的模式(部分原因是我提到的内存泄漏),但我希望这能解释为什么你没有得到堆栈溢出.