这可能是F#中最基本的东西之一,但我刚刚意识到我不知道这些情况背后会发生什么。
let testMe() =
async { printfn "before!"
do! myAsyncFunction() // Waits without blocking thread
printfn "after!" }
testMe |> Async.RunSynchronously
do! myAsyncFunction()
发生了什么?我知道它等待myAsyncFunction
完成,然后继续前进。但是如何在不阻塞线程的情况下做到这一点?
我最好的猜测是,do! myAsyncFunction()
之后的所有内容都作为延续传递,在myAsyncFunction()
完成执行后,会在同一个线程myAsyncFunction()
上执行... {再说一遍,这只是猜测。
答案 0 :(得分:5)
正如您正确指出的那样,myAsyncFunction
传递了一个延续,并在完成时调用它以恢复其余的异步工作流。
通过查看代码的desugared版本,您可以更好地理解它:
let testMe() =
async.Delay(fun () ->
printfn "before!"
async.Bind(myAsyncFunction(), fun () ->
printfn "after!"
async.Zero()))
他们关键的一点是,myAsyncFunction
创建的异步工作流被赋予启动它的Bind
操作,并为其提供第二个参数(延续)作为工作流完成时调用的函数。如果您简化了很多,那么可以像这样定义异步工作流:
type MyAsync<'T> = (('T -> unit) * (exn -> unit)) -> unit
因此,异步工作流只是一个将一些延续作为参数的函数。当它获得延续时,它会执行某些操作(即创建计时器或启动I / O),然后最终调用这些延续。问题“在哪个线程上称为延续?”是一个有趣的 - 在一个简单的模型中,它取决于你正在开始的MyAsync
- 它可能决定在它想要的任何地方运行它们(即Async.SwithcToNewThread
在新线程上运行它们)。 F#库包含一些额外的处理,使使用工作流程的GUI编程更容易。
您的示例使用Async.RunImmediate
来阻止当前线程,但您也可以使用Async.Start
,它只是启动工作流并在生成时忽略结果。 Async.Start
的实现可能如下所示:
let Start (async:MyAsync<unit>) = async (ignore, ignore)