Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/257.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 我可以递归调用异步函数而不溢出堆栈吗?_C#_Asynchronous - Fatal编程技术网

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

C# 我可以递归调用异步函数而不溢出堆栈吗?,c#,asynchronous,C#,Asynchronous,因为异步函数的返回站点不是调用者,所以我假设这是可行的,但我想我应该验证一下这是安全的,以防万一。如果不是,为什么会溢出堆栈 static async Task CheckAsync(TimeSpan recursiveTimer) { // do some work await Task.Delay(recursiveTimer); CheckAsync(recursiveTimer); } 编辑: 我决定试一试——它看起来并没有溢出堆栈(它现在正在我的机器上运行—

因为异步函数的返回站点不是调用者,所以我假设这是可行的,但我想我应该验证一下这是安全的,以防万一。如果不是,为什么会溢出堆栈

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

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


对于熟悉这种机制的人来说:这听起来正确吗?

它溢出的原因是因为溢出堆栈

10 static async Task CheckAsync(TimeSpan recursiveTimer)
20 {
30    // do some work
40    await Task.Delay(recursiveTimer);
50    CheckAsync(recursiveTimer);
60 }
代码执行将停止

10 20 30 40 50 60                         //CheckAsync(recursiveTimer)
            10 20 30 40 50 60             //CheckAsync(CheckAsync(recursiveTimer))
                        10 20 30 40 50 60 //CheckAsync(CheckAsync(CheckAsync(recursiveTimer))
该方法的内容应类似于:

while(doCheck){
  await Task.Delay(recursiveTimer);
  CheckAsync(recursiveTimer);
}
假设您希望在每次异步调用后等待,并在doCheck为真时执行此操作。我假设您在另一个线程中更改了doCheck


看看:

它对您起作用的原因不是因为调用
CheckAsync
的方式,而是因为您正在等待
任务的结果。延迟
。这将始终返回一个“尚未完成”的任务,因此等待它将安排继续。该延续将在一个有效的空堆栈上触发,因此,您随后进行递归调用并不重要

现在,我认为仍然存在内存泄漏,因为IIRC框架将跟踪一个“逻辑堆栈”,它将变得越来越大。。。但它将存储在堆中并展开,直到内存耗尽

如果希望看到堆栈爆炸,只需将代码更改为:

static async Task CheckAsync(TimeSpan recursiveTimer)
{
    // Whatever
    await Task.FromResult(5);
    CheckAsync(recursiveTimer);
}
在这一点上,假设“whatever”中的代码没有等待任何东西,您将拥有完全同步的代码,只需使用
Task
来跟踪完成和异常


我当然不建议将此作为重复工作的模式(部分原因是我提到的内存泄漏),但我希望这可以解释为什么不会出现堆栈溢出。

为什么不在工作时尝试一下呢?如果它溢出堆栈,它将在几秒钟内抛出。这不应该是
await CheckAsync(递归计时器)
?@jon skeet特别不是
await
!这个想法是,如果我不等待
CheckAsync
,我假设返回位置不是调用站点,而是async/wait管道中的某个东西,仅仅因为这个站点名为StackOverflow并不意味着你应该询问堆栈溢出…我开玩笑我开玩笑…我想知道
.ContinueWith(x=>CheckAsync)
一次任务延迟后等于您的代码。感谢您的文章参考!它似乎没有特别提到递归。我决定试试看——看起来它不会溢出堆栈。我假设的原因是,因为CheckAsync函数的返回站点实际上不是CheckAsync,而是异步管道中的某个地方。因此,当CheckAsync调用CheckAsync时,它实际上并不是通过正常的函数调用机制添加到调用堆栈中,而是将函数作为对象放在某个异步“待执行”队列中,该队列通过其他线程管理异步函数运行。很多时候,我都在考虑递归,而简单的循环就足够了。您的示例将受益于添加某种取消。尽管如此,它确实爆炸了!即使没有与“逻辑堆栈”相关的内存问题,我也倾向于不这样做,因为下一个研究它的开发人员可能不会认为“嘿,那
任务。延迟
可能是其他的事情”。你知道一个很好的链接来描述堆栈/延续是如何工作的吗?我有点难以理解它。@Rollie:你可以从这里开始:-或者,我有一个关于它的Pluralsight课程,第三版C#Depth的第15章对它进行了相当广泛的介绍:)我尝试运行以下代码,但没有发现进程大小像我预期的那样增加。我有近300000个电话,我的应用程序大小不到3MB,并且没有线性增长。当然,我使用ContinueWith而不是Wait
private static void callme(){if(++counter%1000==0)Console.WriteLine(counter);if(counter==long.MaxValue)return;Task.Delay(1).ContinueWith(x=>callme());}
@arviman:我怀疑旧任务在创建新任务时被垃圾收集。不过只是猜测而已。@JonSkeet:这似乎是唯一的解释。我最后运行了它,直到有一百万次呼叫,它运行良好,没有内存膨胀。猜测任务正在线程池中循环。