Remix.run Logo
seanalltogether 21 hours ago

One of the things that really took me a long time to map in my head correctly is that in theory async/await should NOT be the same as spinning up a new thread (across most languages). It's just suspending that closure on the current thread and coming back around to it on the next loop of that existing thread. It makes certain data reads and writes safe in a way that multithreading doesn't. However, as noted in article, it is possible to eject a task onto a different thread and then deal with data access across those boundaries. But that is an enhancement to the model, not the default.

jen20 19 hours ago | parent [-]

EDIT: Seems like newer versions of Xcode change the Swift language defaults here, but that is just the IDE, not the language (and Swift Package Manager does not appear to do the same!)

I'd argue the default is that work _does_ move across system threads, and single-threaded async/await is the uncommon case.

Whether async "tasks" move across system threads is a property of the executor - by default C#, Swift and Go (though without the explicit syntax) all have work-stealing executors that _do_ move work between threads.

In Rust, you typically are more explicit about that choice, since you construct the executor in your "own" [1] code and can make certain optimizations such as not making futures Send if you build a single threaded one, again depending on the constraints of the executor.

You can see this in action in Swift with this kind of program:

    import Foundation
    
    for i in 1...100 {
      Task {
        let originalThread = Thread.current
        try? await Task.sleep(for: Duration.seconds(1))
        if Thread.current != originalThread {
          print("Task \(i) moved from \(originalThread) to \(Thread.current)")
        }
      }
    }
    
    RunLoop.main.run()
Note to run it as-is you have to use a version of Swift < 6.0, which has prevented Thread.current being exposed in asynchronous context.

[1]: I'm counting the output of a macro here as your "own" code.