Î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 stareaSCHEDULED
şi apoi înRUNNING
.
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):
ExcecutorService.submit(task)
;task.run()
;new Thread(task).start()
;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:
Executor
, dacă este specificat pentru serviciul dat,Executor
nu este specificat, ThreadPoolExecutor
.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-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ă!