假设我们有这个可能无限的工作流程:
let workAsync i = async {
printfn "Working... %A" i
if i > 3 then
failwith "errg"
elif i = -1000 then // ensure work is async
do! Async.Sleep 0
return i+1
}
let workflow =
async {
let mutable i = 0
while true do // I can't quit you!
let! j = workAsync i
i <- j
//do! Async.Sleep 0 // This is important?
} |> Async.RunSynchronously
如果运行此命令,我们将得到预期的异常。 注意堆栈跟踪的增长方式。可以将其增大。
Working... 0
Working... 1
Working... 2
Working... 3
Working... 4
> System.Exception: errg
at FSI_0017.workAsync@155-45.Invoke(Unit unitVar) in C:\work\website_tq\tqit7\fs\Scripts\fsi_basic.fsx:line 157
at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvoke[T,TResult](AsyncActivation`1 ctxt, TResult result1, FSharpFunc`2 part2)
at Microsoft.FSharp.Control.AsyncPrimitives.unitAsync@607.Invoke(AsyncActivation`1 ctxt)
at FSI_0017.workAsync@158-47.Invoke(AsyncActivation`1 ctxt) in C:\work\website_tq\tqit7\fs\Scripts\fsi_basic.fsx:line 158
at Microsoft.FSharp.Control.AsyncPrimitives.unitAsync@607.Invoke(AsyncActivation`1 ctxt)
at Microsoft.FSharp.Control.AsyncPrimitives.unitAsync@607.Invoke(AsyncActivation`1 ctxt)
at FSI_0017.workAsync@158-47.Invoke(AsyncActivation`1 ctxt) in C:\work\website_tq\tqit7\fs\Scripts\fsi_basic.fsx:line 158
at Microsoft.FSharp.Control.AsyncPrimitives.unitAsync@607.Invoke(AsyncActivation`1 ctxt)
at Microsoft.FSharp.Control.AsyncPrimitives.unitAsync@607.Invoke(AsyncActivation`1 ctxt)
at FSI_0017.workAsync@158-47.Invoke(AsyncActivation`1 ctxt) in C:\work\website_tq\tqit7\fs\Scripts\fsi_basic.fsx:line 158
at Microsoft.FSharp.Control.AsyncPrimitives.unitAsync@607.Invoke(AsyncActivation`1 ctxt)
at Microsoft.FSharp.Control.AsyncPrimitives.unitAsync@607.Invoke(AsyncActivation`1 ctxt)
at FSI_0017.workAsync@158-47.Invoke(AsyncActivation`1 ctxt) in C:\work\website_tq\tqit7\fs\Scripts\fsi_basic.fsx:line 158
at Microsoft.FSharp.Control.AsyncPrimitives.unitAsync@607.Invoke(AsyncActivation`1 ctxt)
at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction)
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.FSharp.Control.AsyncResult`1.Commit()
at Microsoft.FSharp.Control.AsyncPrimitives.RunSynchronouslyInAnotherThread[a](CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout)
at Microsoft.FSharp.Control.AsyncPrimitives.RunSynchronously[T](CancellationToken cancellationToken, FSharpAsync`1 computation, FSharpOption`1 timeout)
at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously[T](FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken)
at <StartupCode$FSI_0017>.$FSI_0017.main@() in C:\work\website_tq\tqit7\fs\Scripts\fsi_basic.fsx:line 161
但是,如果我们取消注释Async.Sleep行,则堆栈不会增长:
Working... 0
Working... 1
Working... 2
Working... 3
Working... 4
> System.Exception: errg
at FSI_0002.workAsync@155.Invoke(Unit unitVar) in C:\work\website_tq\tqit7\fs\Scripts\fsi_basic.fsx:line 157
at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvoke[T,TResult](AsyncActivation`1 ctxt, TResult result1, FSharpFunc`2 part2)
at FSI_0002.workflow@167-5.Invoke(AsyncActivation`1 ctxt) in C:\work\website_tq\tqit7\fs\Scripts\fsi_basic.fsx:line 167
at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction)
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.FSharp.Control.AsyncResult`1.Commit()
at Microsoft.FSharp.Control.AsyncPrimitives.RunSynchronouslyInAnotherThread[a](CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout)
at Microsoft.FSharp.Control.AsyncPrimitives.RunSynchronously[T](CancellationToken cancellationToken, FSharpAsync`1 computation, FSharpOption`1 timeout)
at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously[T](FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken)
at <StartupCode$FSI_0002>.$FSI_0002.main@() in C:\work\website_tq\tqit7\fs\Scripts\fsi_basic.fsx:line 161
*修改:* 更改了workAsync
,以确保异步性没有得到优化。
答案 0 :(得分:2)
之所以会发生这种情况,是因为当Sleep
被注释掉时,您的工作流程中实际上没有异步。
一切都是完全同步的,但是由于它是在async
计算表达式中编码的,因此它变得怪异地嵌套了。可见,每一行let!
实际上都会调用右侧的任何内容(在您的示例中为workAsync
),并向其传递一个回调,一旦完成异步部分,就需要调用该回调。回调包含其余代码-在let!
行之后立即开始的继续。编译器对代码执行巧妙的转换,以使其看起来既美观又线性,而实际上却是一系列回调。
但是,由于workAsync
实际上不是异步的,因此它会立即调用回调,然后回调转而调用workAsync
的下一个迭代,依此类推。这样您的堆栈就会增加。
但是等等!毕竟它实际上不应该增长。回调的调用是workAsync
中的最后一个调用(也称为“尾调用”),并且.NETCore和.NET Framework都消除了这些调用(实际上:在我的机器上,我无法重现您的结果)。我唯一可以提供的推测是,您必须在Mono上运行此代码,但这并不能总是消除尾声。
但是,如果您取消注释Sleep
,则它将成为断点。 Sleep
实际上是异步的,这意味着它计划在超时后在新线程上执行回调。该执行从头开始,使用新堆栈,因此即使不消除尾部调用,堆栈也不会增长。
所以要回答您的原始问题:不,无尽的异步计算不会溢出堆栈,除非它实际上不是异步的并且在Mono上运行。