Take JavaFX for example:
1. Rendering is done on a background thread in parallel with the app thread.
2. The class lib makes it easy to run background tasks that update the GUI to reflect progress and results.
3. You can construct widget trees on background threads and then pass them to the main UI thread only to attach them/make them visible.
With libs like ReactFX you can take event streams from the UI, map over them to a worker thread or pool of threads, and pass the results back to the UI thread.
A few years ago I did a video tutorial showing a few of these things, the code can be seen here:
https://github.com/mikehearn/KotlinFPWebinar/blob/master/src...
Basically this is a simple auto-complete widget. It takes a stream of values from a text field the user is typing in, reduces the frequency of changes (so if you type fast it doesn't constantly do work it's about to throw away) and then passes the heavy work of computing completions onto a background thread, then takes the results back onto the UI thread.
val edits = EventStreams.valuesOf(edit.textProperty()).forgetful()
val fewerEdits = edits.successionEnds(Duration.ofMillis(100)).filter { it.isNotBlank() }
val inBackground = fewerEdits.threadBridgeFromFx(thread)
val completions = inBackground.map { ngrams.complete(it) }
val inForeground = completions.threadBridgeToFx(thread)
val lastResult = inForeground.map {
FXCollections.observableList(it)
}.toBinding(FXCollections.emptyObservableList())
completionsList.itemsProperty().bind(lastResult)
Now if you mean what UI libraries support writes to properties from arbitrary background threads without an explicit thread switch, I think the answer is only Win32 and Motif. But that's not a particularly important feature. JavaFX can catch the case if you access live UI from the wrong thread so such bugs are rarely an issue in practice. In particular it's easy to synchronize with the UI thread with a few simple helpers, e.g.: thread(name = "background") {
while (true) {
var speed = synchronizeWithUI { speedSlider.value }
doSomeWork(speed)
}
}