Может бесконечное асинхронное переполнение стека - PullRequest
0 голосов
/ 19 января 2019

Предположим, у нас есть потенциально бесконечный рабочий процесс:

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, чтобы гарантировать, что асинхронность не оптимизирована.

1 Ответ

0 голосов
/ 19 января 2019

Это происходит потому, что в вашем рабочем процессе нет ничего асинхронного, когда Sleep закомментирован.

Все полностью синхронно, но поскольку оно закодировано в вычислительном выражении async, оно становится странно вложенным.Видите, каждая строка let! на самом деле вызывает все, что находится справа (в вашем примере - workAsync) и передает ей обратный вызов, который должен быть вызван после выполнения асинхронной части.Обратный вызов содержит остальную часть кода - продолжение, начинающееся сразу после строки let!.Компилятор выполняет умные преобразования кода, чтобы он выглядел все красиво и линейно, хотя на самом деле это серия обратных вызовов.

Однако, поскольку workAsync на самом деле не асинхронный, он просто вызывает обратный вызов сразуи обратный вызов оборачивается и вызывает следующую итерацию workAsync и так далее.И так ваш стек растет.

Но подождите!Это на самом деле не должно расти в конце концов.Вызов обратного вызова является последним вызовом в workAsync, также известном как «хвостовой вызов», и и .NETCore, и .NET Framework их устраняют (и действительно: на моем компьютере я не могу воспроизвести ваш результат).Единственное предположение, которое я могу предложить, это то, что вы должны запускать это на Mono, что не всегда устраняет хвостовые вызовы.

Однако, если вы раскомментируете Sleep, это становится переломным моментом.Sleep является на самом деле асинхронным, что означает, что он планирует выполнение обратного вызова в новом потоке после истечения времени ожидания.Это выполнение начинается с нуля, со свежим стеком, и поэтому стек не растет, даже когда хвостовые вызовы не исключены.

Итак, чтобы ответить на ваш первоначальный вопрос: нет, нетбесконечное асинхронное вычисление не может переполнить стек, кроме случаев, когда оно фактически не асинхронно и выполняется в Mono.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...