Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async.StartImmediate vs Async.RunSynchronously

Tags:

f#

As my limited (or even wrong) understanding, both Async.StartImmediate and Async.RunSynchronously start an async computation on current thread. Then what is exactly the difference between these two functions? Can anyone help explain?

Update:

After looking into F# source code at https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/control.fs, I think I kind of understand what happens. Async.StartImmediate starts the async on the current thread. After it hits an async binding, whether it will continue to run on the current thread depends on the async binding itself. For example, if the async binding calls Async.SwitchToThreadPool, it will run on ThreadPool instead of the current thread. In this case, you will need to call Async.SwitchToContext if you want to go back to the current thread. Otherwise, if the async binding doesn’t do any switch to other threads, Async.StartImmediate will continue to execute the async binding on the current thread. In this case, there is no need to call Async.SwitchToContext if you simply want to stay on the current thread.

The reason why Dax Fohl’s example works on GUI thread is because Async.Sleep carefully captures the SynchronizationContext.Current and makes sure the continuation run in the captured context using SynchronizationContext.Post(). See https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/control.fs#L1631, where unprotectedPrimitiveWithResync wrapper changes the “args.cont” (the continuation) to be a Post to the captured context (see: https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/control.fs#L1008 — trampolineHolder.Post is basically SynchronizationContext.Post). This will only work when SynchronizationContext.Current is not null, which is always the case for GUI thread. Especially, if you run in a console app with StartImmediate, you will find Async.Sleep will indeed go to ThreadPool, because the main thread in console app doesn’t have SynchronizationContext.Current.

So to summarize, this indeed works with GUI thread because certain functions like Async.Sleep, Async.AwaitWaitHandle etc carefully capture and makes sure to post back to the previous context. It looks this is a deliberate behavior, however this doesn’t seem to be documented anywhere in the MSDN.

like image 619
appthumb Avatar asked Sep 17 '16 12:09

appthumb


1 Answers

Async.RunSynchronously waits until the entire computation is completed. So use this any time you need to run an async computation from regular code and need to wait for the result. Simple enough.

Async.StartImmediate ensures that the computation is run within the current context but doesn't wait until the entire expression is finished. The most common use for this (for me, at least) is when you want to run a computation on the GUI thread, asynchronously. For example if you wanted to do three things on the GUI thread at 1-second intervals, you could write

async {
  do! Async.Sleep 1000
  doThing1()
  do! Async.Sleep 1000
  doThing2()
  do! Async.Sleep 1000
  doThing3()
} |> Async.StartImmediate

That will ensure everything gets called in the GUI thread (assuming you call that from the GUI thread), but won't block the GUI thread for the whole 3 seconds. If you use RunSynchronously there, it'll block the GUI thread for the duration and your screen will become unresponsive.

(If you haven't done GUI programming, then just note that updates to GUI controls all have to be done from the same thread, which can be difficult to coordinate manually; the above takes away a lot of the pain).

To give another example, here:

// Async.StartImmediate
async {
  printfn "Running"
  do! Async.Sleep 1000
  printfn "Finished"
} |> Async.StartImmediate
printfn "Next"

> Running
> Next
// 1 sec later
> Finished

// Async.RunSynchronously
async {
  printfn "Running"
  do! Async.Sleep 1000
  printfn "Finished"
} |> Async.RunSynchronously
printfn "Next"

> Running
// 1 sec later
> Finished
> Next

// Async.Start just for completion:
async {
  printfn "Running"
  do! Async.Sleep 1000
  printfn "Finished"
} |> Async.Start
printfn "Next"

> Next
> Running // With possible race condition since they're two different threads.
// 1 sec later
> Finished

Also note that Async.StartImmediate can't return a value (since it doesn't run to completion before continuing), whereas RunSynchronously can.

like image 128
Dax Fohl Avatar answered Nov 07 '22 10:11

Dax Fohl