代码之家  ›  专栏  ›  技术社区  ›  Ray

可以无限异步工作流溢出堆栈吗

  •  1
  • Ray  · 技术社区  · 7 年前

    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
    

    如果运行此操作,我们将得到预期的异常。 注意stacktrace是如何增长的。

    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

    1 回复  |  直到 7 年前
        1
  •  2
  •   Fyodor Soikin    7 年前

    发生这种情况的原因是,当 Sleep 被注释掉了。

    一切都是完全同步的,但因为它是在 async let! 行实际上调用右边的任何内容(在您的示例中)- workAsync 允许

    但是, 异步工作 实际上并不是异步的,它只是立即调用回调,然后回调返回并调用 异步工作

    但是等等!事实上,它毕竟不应该生长。回调调用是中的最后一个调用 异步工作 -也称为“tail call”..NETCore和.netframework都消除了这些功能(事实上:在我的机器上,我无法复制您的结果)。我能提供的唯一推测是,你必须在Mono上运行它,它并不总是消除尾部调用。

    如果你取消注释 睡觉 然而,它却成了一个转折点。 异步,这意味着它计划在超时后在新线程上执行回调。该执行从零开始,使用新的堆栈,因此即使没有消除尾部调用,堆栈也不会增长。

    因此,要回答您最初的问题: 不,一个无休止的异步计算不能溢出堆栈,除非它实际上不是异步的并且在Mono上运行。