Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does `Async.StartImmediate` run the `async` process on the same thread throughout the lifetime of the process?

This is in reference to F#'s Async.StartImmediate method. Possibly a diversion, but this method is confusingly named because Async.Start also starts the async process immediately, just on a thread pool.

Anyway, the documentation states that Async.StartImmediate starts the process using the calling thread. Does the async process continue to execute on that same thread throughout the lifetime of the process? Or is it possible it switches at some point? To my knowledge, Async.Start allows the process to switch underlying threads since it runs on top of a thread pool.

Edit: To clarify the question, I am thinking about an async that doesn't contain any other usage of async, let!, do!, return!, etc. for example:

async { printfn "testing" }
like image 294
bmitc Avatar asked Nov 01 '25 06:11

bmitc


1 Answers

As you already found, the difference is that StartImmediate runs the prefix on the current thread while Start switches immediately. To get an actual continuation, you need a "real"* async operation, e.g. Async.Sleep. async { } by itself is not async but lets you use async. That is, the difference is visible with

open System.Threading

Thread.CurrentThread.Name <- "Main"

let computation s = async {
    printfn "async prefix %s: %s" s Thread.CurrentThread.Name
    do! Async.Sleep 1
    printfn "async continuation %s: %s" s Thread.CurrentThread.Name
}

computation "StartImmediate" |> Async.StartImmediate
computation "Start" |> Async.Start

which prints

async prefix StartImmediate: Main
async prefix Start: .NET ThreadPool Worker
async continuation StartImmediate: .NET ThreadPool Worker
async continuation Start: .NET ThreadPool Worker

Note that the two operations are interleaved now and the continuation is run on the thread pool in both cases.

* Update on "real" async

What do I mean by "real" async? async is essentially a way to write "Here is some code, run that, if you encounter IO, go do something else and once the OS signals 'done' continue the remaining work." Now, as long as there is no IO (reading/writing from/to a file, stream, DB, waiting for a timer, ...) the current thread (in do!, Async.StartImmediate) will run the "prefix" code (potentially all code including inner do! async {). Once a "real" async operation is encountered, the "continuation" (the code after the IO operation) is scheduled and the former thread is free to do something else. Once the operation is completed the continuation is executed on the thread pool.

That is except for UI programs (WPF, WinForms, ...) where it is oftentimes desirable to run the continuation on the original thread (UI), because only that is allowed to write to the UI respectively shared data structures. In order to make that happen there needs to be a SynchronizationContext and some kind of event loop (the original thread needs to check whether there's work waiting).

In practice it is more complicated due to (amongst others) C# having 'hot' (already running) Tasks, F# cold (reusable) asyncs and both being used also for parallelism (CPU bound work distributed across multiple cores).

like image 135
CaringDev Avatar answered Nov 02 '25 21:11

CaringDev