As you pointed out correctly, myAsyncFunction is passed a continuation, and it calls it to resume the rest of the asynchronous workflow when it finishes.
You can understand this better by looking at the stiff version of the code:
let testMe() = async.Delay(fun () -> printfn "before!" async.Bind(myAsyncFunction(), fun () -> printfn "after!" async.Zero()))
The key point is that the asynchronous workflow created by myAsyncFunction is assigned to the Bind operation, which starts it, and gives it a second argument (continued) as a function to call when the workflow terminates. If you simplify a lot, then an asynchronous workflow can be defined as follows:
type MyAsync<'T> = (('T -> unit) * (exn -> unit)) -> unit
So, an asynchronous workflow is just a function that takes some extensions as an argument. When it receives continuations, it does something (i.e., creates a timer or starts I / O), and then ultimately calls these continuations. The question "On what thread are the continuations called?" is interesting - in a simple model, it depends on the MyAsync that you run - it can decide to run them anywhere ( Async.SwithcToNewThread launches them in a new thread). The F # library contains some additional processing that facilitates GUI programming using workflows.
Your example uses Async.RunImmediate , which blocks the current thread, but you can also use Async.Start , which only starts the workflow and ignores the result when it is created. An Async.Start implementation might look like this:
let Start (async:MyAsync<unit>) = async (ignore, ignore)
source share