does anybody have a good breakdown on multithreadi...
# javafx
g
does anybody have a good breakdown on multithreading in javafx? I understand that scene graph changes must be done on the event thread, and I guess we should assume that "unless your sure, just do it on the UI thread", but is it safe for me to do something like:
Copy code
launch(Dispatchers.JavaFx){
  val viewData = snapshotViewModelState()
  val newView = async(Dispatchers.Default){
    val vbox = VBox()
    vbox.children += makeCoolElements(viewData)
  }

  this.view.content = newView
}
or
Copy code
launch(Dispatchers.JavaFx){
  val viewData = snapshotViewModelState()
  val fxmlLoader = javafx.fxml.FXMLLoader()

  val newView = async(Dispatchers.Default){
    fxmlLoader.load(findresource("/com/mycompany/reasonably-standard-fxml-that-doesnt-contain-stages.fxml")) //this is actually annoying since defining an alert in FXML would be handy
  }

  this.view.content = newView
}
that is to say, is it safe for me to call constructors and/or mutate view objects off the UI thread if I'm certain they're not on a scene graph?
m
Yes, it's safe because they are not on a scene graph, so you are not changing the scene graph. I do it routinely in my application. Creating significant part of the UI on secondary dispatcher (Default, usually), then switching back to the Dispatchers.JavaFX to add the to the scene graph, improves the responsiveness and it's correct. You can just create a suspend function and switch dispatcher using
withContext
, depending on what you are doing. I have three functions called
io
,
computation
and
javafx
which do just that:
Copy code
suspend fun <T> computation(
    block: suspend CoroutineScope.() -> T
): T =
    withContext(Dispatchers.Default, block = block)

suspend fun <T> io(
    block: suspend CoroutineScope.() -> T
): T =
    withContext(<http://Dispatchers.IO|Dispatchers.IO>, block = block)


suspend fun <T> javafx(
    block: suspend CoroutineScope.() -> T
): T =
    withContext(Dispatchers.JavaFx, block = block)
Here I populate a
ScrollPane
with a content which is created by scanning the filesystem:
Copy code
suspend fun populate(repository: SampleRepository) {
    val buttons = io {
        repository.mapAllSampleGroups().map { group ->
            VBox().apply {
                spacing = 5.0
                padding = insets(5.0)
                addClass(ClipBrowserStylesheet.card2)
                children.setAll(
                    Label(group.name),
                    HBox().apply {
                        spacing = 5.0
                        padding = insets(5.0)
                        children.setAll(
                            group.samples.mapIndexed { index, sample ->
                                Button("${index + 1}").apply {
                                    setOnAction {
                                        onClick?.invoke(sample)
                                    }
                                }
                            }
                        )
                    }
                )
            }
        }
    }

    content = FlowPane().apply {
        setGapsAndPadding(5.0)
        children.addAll(buttons)
    }
}