ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 150
Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 28
Abonament PDF

Concurență şi data binding în JavaFX

Silviu Dumitrescu
Line manager@Telenav



Diana Bălan
Map analyst@Telenav



PROGRAMARE


În articolul din acest număr vă aducem din nou în atenție provocări tehnologice din lumea JavaFX. În articolul al doilea vom discuta despre concurență şi data binding.

Pachetul javafx.concurrent gestionează codul multifir al interacțiunii cu UI-ul şi asigură că această interacțiune are loc în firul corect. Pachetul constă din interfața Worker şi două clase de bază Task şi Service, ambele implementând interfața Worker.

Interfața Woker furnizează API-ul folosit de un "background worker" ce comunică cu UI-ul. Clasa Task este o implementare complet observabilă a clasei java.util.concurrent.FutureTask şi permite dezvoltatorilor să implementeze task-uri asincrone în aplicațiile JavaFX. Clasa Service execută aceste task-uri.

Un Worker este asadar un obiect ce lucrează într-un fir din background. Starea obiectului Worker este observabilă și utilizabilă din firele aplicației JavaFX.

Ciclul de viață al lui Worker este definit astfel: când este creat obiectul Worker, acesta este în starea READY. După ce a fost programat pentru lucru, obiectul Worker tranzitează către starea SCHEDULED. După aceea, când obiectul Worker rulează, starea sa devine RUNNING.

Observație: chiar dacă obiectul Worker a pornit imediat, fără a fi programat, el tranzitează mai întâi în starea SCHEDULED şi apoi în RUNNING.

Starea obiectului Worker, atunci când se execută cu succes devine SUCCEEDED, iar proprietatea valoare va fi setată la rezultatul obiectului Worker. Altfel, dacă sunt aruncate excepții pe timpul execuției obiectului Worker, starea sa devine FAILED, iar proprietatea excepție este setată la tipul de excepție apărut. În orice stare obiectul Worker poate fi întrerupt utilizând metoda cancel(), ceea ce trimite obiectul în starea CANCELLED.

Progresul înregistrat la rularea obiectului Worker poate fi obținut prin trei proprietăți diferite: totalWork, workDone şi progress.

Clasa Task poate fi pornită în unul dintre următoarele moduri (primele două ar fi preferabile):

Taskurile sunt utilizate pentru a implementa logica de lucru într-un fir din background. Pentru început trebuie să extindem clasa Task, care va suprascrie metoda call(). Clasa Task moștenește clasa java.utils.concurrent.FutureTask, ce implementează interfața Runnable. De aceea obiectul Task poate fi utilizat cu API-ul Executor şi poate fi trimis unui fir ca parametru.

Putem apela obiectul Task direct prin FutureTask.run(), ceea ce ne permite să apelăm acest task dintr-un alt fir.

Vom crea o clasă CounterTask ce extinde clasa Task.

public class CounterTask extends Task {
    @Override
    public Void call() {
        final int max = 10000000;
        updateProgress(0, max);
        for (int i = 1; i <= max; i++) {
            updateProgress(i, max);
        }
        return null;
    }
}

Metoda call() este invocată de firul din background, de aceea această metodă poate manipula stări ce sunt sigure a fi citite sau scrise dintr-un fir din background. Spre exemplu, manipularea scenei grafice active din metoda call() va arunca o runtime exception.

Pe de altă parte, clasa Task este destinată a fi utilizată cu aplicații JavaFX şi ne asigură că orice modificări ale proprietăților publice, notificări de eroare, manipulatoare de evenimente şi stări apar în firul aplicației JavaFX. În interiorul metodei call() putem utiliza metodele: updateProgress(), updateMessage() şi updateTitle() pentru a actualiza valorile corespunzătoare proprietăților pe firul JavaFX. În aplicație am creat o instanță a clasei anterioare, numită countTask şi am executat-o printr-un ExecutorService (ExecutorService es = Executors.newSingleThreadExecutor();):

@Override
public void handle(ActionEvent event) {
    System.out.println("Count Started");
    bar.progressProperty().bind(countTask.progressProperty());
    es.execute(countTask);
}

Clasa Service este destinată executării unui obiect Task dintr-unul sau mai multe fire. Metodele şi stările clasei Service trebuie accesate din firul aplicației JavaFX. Această clasă ajută dezvoltatorii să implementeze o interacțiune corectă între firele din background şi firul aplicației JavaFX. Putem porni, opri, anula şi restarta un Service. Un Service poate rula un task mai mult de o dată. Așadar, un serviciu poate fi definit declarativ şi restartat la cerere.

Un Service poate fi executat în unul dintre următoarele modalități:

Exemplu de creare a unui service custom este dat în exemplul de mai jos:

public class CounterService extends Service {
    @Override
    protected Task createTask() {
        CounterTask ct = new CounterTask();
        return ct;
    }
}

Creăm în aplicația JavaFX un CounterService (CounterService cs = new CounterService();) şi pornim firul astfel:

if (cs.getState() == State.READY) {
    cs.start();
}

Data Binding

Data binding-ul are rolul de a simplifica task-ul sincronizând view-ul cu datele din model. Legarea (binding-ul) observă listele sale de dependențe pentru a detecta schimbări şi se actualizează dacă acestea au apărut. API-ul de binding furnizează un mod simplu de a crea legări pentru cele mai comune situații.

Binding-ul este așadar un mecanism puternic pentru exprimarea relațiilor directe între variabile. Când obiectele participă la legări, modificările efectuate unuia vor fi automat reflectate celuilalt. Spre exemplu, binding-ul poate fi utilizat în GUI pentru păstrarea automată a afișărilor sincronizate cu datele pe care le referă.

Binding-urile sunt asamblate din una sau mai multe surse numite dependențe.

În exemplul nostru anterior am folosit funcția bind() pentru a lega progress bar-ul de counterTask. Iată codul complet al aplicației JavaFX:

public class CounterBarAppService extends Application {
    StackPane root = new StackPane();
    VBox mainBox = new VBox();
    ProgressBar bar = new ProgressBar(0.0);
    CounterService cs = new CounterService();

    @Override
    public void init() throws Exception {
        super.init();

        mainBox.setAlignment(Pos.CENTER);
        mainBox.setSpacing(10);

        Button btn = new Button();
        btn.setText("Count to Ten Million!");
        btn.setOnAction(new EventHandler() {

            @Override
            public void handle(ActionEvent event) {
                System.out.println("Count Started");
                bar.progressProperty().bind(cs.progressProperty());
                if (cs.getState() == State.READY) {
                    cs.start();
                }
            }
        });

        Button restartBtn = new Button();
        restartBtn.setText("Restart");
        restartBtn.setOnAction(new EventHandler() {

            @Override
            public void handle(ActionEvent event) {
                System.out.println("Count Started");
                bar.progressProperty().bind(cs.progressProperty());
                cs.restart();
            }
        });

        mainBox.getChildren().add(btn);
        mainBox.getChildren().add(restartBtn);
        mainBox.getChildren().add(bar);
        root.getChildren().add(mainBox);
    }

    @Override
    public void stop() throws Exception {
        super.stop();
    }

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("JavaFX Service Example");

        primaryStage.setScene(new Scene(root, 400, 250));
        primaryStage.show();
    }
}

Vă așteptăm cu mare plăcere la discuții despre noua lume JavaFX.

Lectură plăcută!

NUMĂRUL 149 - Development with AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects