Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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() i updateValue() es poden cridar des de call() i publiquen el valor al fil de JavaFX. No cal runLater().
  • Els gestors setOnSucceeded(), setOnFailed() i setOnCancelled() s’executen al fil de JavaFX, de manera que hi pots tocar la interfície directament.
  • cancel() marca la tasca, i el call() ho ha de comprovar amb isCancelled() 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().

Referències

Last change: , commit: 0ee48c2