Concurrència
JavaFX processa tots els esdeveniments de la interfície en un únic fil, el JavaFX Application Thread. Aquest fil és el que crida els teus gestors d’esdeveniments i el que repinta la pantalla.
D’aquí surten les dues regles que governen tota aplicació gràfica:
- Només aquest fil pot tocar un graf d’escena que ja es mostra. Els nodes no són thread-safe, i és una decisió deliberada: sense sincronització, el dibuix és molt més ràpid. El preu és que accedir-hi des d’un altre fil deixa la interfície en un estat inconsistent, sovint amb una excepció.
- Aquest fil no pot fer feina llarga. Mentre executa el teu codi no processa esdeveniments ni repinta, de manera que l’aplicació es queda congelada. Una consulta a una base de dades o una descàrrega dins d’un gestor de botó és exactament això.
La sortida és la de sempre: la feina llarga va a un altre fil, i el resultat torna al fil de JavaFX per mostrar-lo. Els conceptes de fons són els de Processos i fils.
Platform.runLater
Platform.runLater() posa una tasca a la cua del fil de JavaFX. És el mecanisme bàsic per actualitzar la interfície des d’un fil de fons:
new Thread(() -> {
String resultat = consultaLlarga(); // fil de fons
Platform.runLater(() -> label.setText(resultat)); // fil de JavaFX
}).start();
Funciona, però deixa la feina de coordinació a les teves mans: saber si la tasca ha acabat, mostrar el progrés, cancel·lar-la o recollir l’excepció que hagi llançat.
Task
El paquet javafx.concurrent resol precisament això. Un Task<V> és una feina que s’executa en un fil de fons i que exposa el seu estat com a propietats observables, ja preparades per lligar-les a la interfície.
Task<String> tasca = new Task<>() {
@Override
protected String call() throws Exception {
for (int i = 0; i < 100; i++) {
updateProgress(i, 100);
updateMessage("Processant " + i);
Thread.sleep(50);
}
return "fet";
}
};
progressBar.progressProperty().bind(tasca.progressProperty());
label.textProperty().bind(tasca.messageProperty());
tasca.setOnSucceeded(e -> mostraResultat(tasca.getValue()));
tasca.setOnFailed(e -> mostraError(tasca.getException()));
Thread fil = new Thread(tasca);
fil.setDaemon(true);
fil.start();
El mètode call() s’executa al fil de fons. La resta ja el gestiona JavaFX:
updateProgress(),updateMessage()iupdateValue()es poden cridar des decall()i publiquen el valor al fil de JavaFX. No calrunLater().- Els gestors
setOnSucceeded(),setOnFailed()isetOnCancelled()s’executen al fil de JavaFX, de manera que hi pots tocar la interfície directament. cancel()marca la tasca, i elcall()ho ha de comprovar ambisCancelled()per aturar-se de veritat.
Un Task és d’un sol ús. Si la feina s’ha de poder repetir, com un refresc periòdic, el que vols és un Service: fabrica un Task nou cada vegada i es pot arrencar i reiniciar amb start() i restart().