Javafx Service (Thread)

23
Tutoriel sur l'exécution d'une tâche de fond en JavaFX Comment lancer un traitement lourd en arrière-plan pour ne pas bloquer son UI JavaFX. Par Fabrice Bouyé Date de publication : 18 mars 2015 Dernière mise à jour : 24 mars 2015 Cet article a pour but de vous expliquer comment exécuter une tâche de fond en JavaFX sans avoir à recourir à un Thread lancée manuellement et sans bloquer l'exécution du thread principal de gestion des événements. Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum Commentez

description

JavaFx Thread Service

Transcript of Javafx Service (Thread)

Page 1: Javafx Service (Thread)

Tutoriel sur l'exécution d'unetâche de fond en JavaFX

Comment lancer un traitement lourd en arrière-plan pour ne pas bloquer son UI JavaFX.

Par Fabrice Bouyé

Date de publication : 18 mars 2015

Dernière mise à jour : 24 mars 2015

Cet article a pour but de vous expliquer comment exécuter une tâche de fond en JavaFXsans avoir à recourir à un Thread lancée manuellement et sans bloquer l'exécution du threadprincipal de gestion des événements.

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forumCommentez

Page 2: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 2 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

I - Introduction..............................................................................................................................................................3II - Problématique........................................................................................................................................................ 3

II-A - Exemple.........................................................................................................................................................3II-B - Raisonnement............................................................................................................................................... 6II-C - Solution......................................................................................................................................................... 6

III - Interfaces et classes importantes....................................................................................................................... 10III-A - javafx.concurrent.Worker<V>..................................................................................................................... 10III-B - javafx.concurrent.Service<V>.....................................................................................................................10III-C - javafx.concurrent.Task<V>......................................................................................................................... 11III-D - Javafx.concurrent.ScheduledService<V>...................................................................................................11

IV - Utiliser un service............................................................................................................................................... 12IV-A - Création et lancement............................................................................................................................... 12IV-B - Annulation.................................................................................................................................................. 12IV-C - Relancer un service...................................................................................................................................13IV-D - Avancement du service............................................................................................................................. 13IV-E - État du service...........................................................................................................................................13

IV-E-1 - ChangeListener sur l'état du service................................................................................................ 14IV-E-2 - Callback sur le service......................................................................................................................15

IV-F - Récupérer le résultat................................................................................................................................. 16IV-G - Récupérer la raison de l'échec................................................................................................................. 16

V - Utiliser une tâche.................................................................................................................................................17V-A - Notification de l'avancement.......................................................................................................................17V-B - Annulation................................................................................................................................................... 17

V-B-1 - Lors d'un appel bloquant................................................................................................................... 18V-C - État d'une tâche......................................................................................................................................... 18V-D - Produire des résultats partiels....................................................................................................................19

V-D-1 - JDK 7................................................................................................................................................. 19V-D-2 - JDK 8................................................................................................................................................. 20

V-E - Utiliser une tâche dans un contexte non JavaFX...................................................................................... 21VI - Utiliser un service répétable...............................................................................................................................21

VI-A - Délai avant l'exécution...............................................................................................................................21VI-B - Période d'exécution................................................................................................................................... 22VI-C - Gestion des échecs...................................................................................................................................22VI-D - Derniers bons résultats............................................................................................................................. 22

VII - Conclusion......................................................................................................................................................... 22VIII - Remerciements................................................................................................................................................. 22IX - Liens....................................................................................................................................................................23

Page 3: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 3 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

I - Introduction

Comme dans n'importe quel logiciel, il peut arriver que l'utilisateur soit amené à exécuter une tâche de longue duréepour par exemple un calcul, charger ou sauvegarder un fichier, effectuer une requête web ou sur une base de donnéesou encore faire une pause pendant une durée déterminée ou effectuer une boucle infinie pour surveiller l'état d'unport, d'un périphérique ou d'un fichier.

Or si on ne prend pas attention à la manière dont ces tâches longues sont traitées, il est très facile de bloquer lefonctionnement de l'UI, ce qui laissera à penser à l'utilisateur que le programme a planté puisque plus rien ne répondà ses saisies. Fort heureusement, l'API JavaFX fournit via son API de concurrence une interface et une paire declasses tout spécialement dédiées à la gestion des tâches de fond, ce qui facilite grandement la gestion de ce genred'opérations.

II - Problématique

II-A - Exemple

Prenons un problème simple : nous disposons d'une UI contenant un simple bouton et nous allons lancer le calculde 1 000 000 d'entiers lorsque nous cliquons sur ce bouton. Nous allons également changer le curseur de l'UI etle remplacer par un curseur d'attente et nous désactiverons les boutons permettant de lancer le calcul pendant quece dernier tourne.

Code JDK71. package test;2. 3. import javafx.application.Application;4. import javafx.application.Platform; 5. import javafx.event.ActionEvent;6. import javafx.event.EventHandler;7. import javafx.scene.Cursor;8. import javafx.scene.Scene;9. import javafx.scene.control.Button;10. import javafx.scene.control.Menu;11. import javafx.scene.control.MenuBar;12. import javafx.scene.control.MenuItem;13. import javafx.scene.layout.BorderPane;14. import javafx.scene.layout.StackPane;15. import javafx.stage.Stage;16. 17. public class Main extends Application {18. 19. private MenuItem calculateItem;20. private Button calculateButton;21. private Scene scene;22. 23. @Override24. public void start(Stage primaryStage) {25. final MenuItem exitItem = new MenuItem("Quitter");26. exitItem.setOnAction(new EventHandler<ActionEvent>() {27. 28. @Override29. public void handle(ActionEvent t) {30. Platform.exit();31. }32. }); final Menu fileMenu = new Menu("Fichier");33. fileMenu.getItems().add(exitItem);34. calculateItem = new MenuItem("Calculer");35. calculateItem.setOnAction(new EventHandler<ActionEvent>() {36. 37. @Override38. public void handle(ActionEvent t) {39. doCalculate();40. }

Page 4: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 4 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

Code JDK741. });42. final Menu actionMenu = new Menu("Action");43. actionMenu.getItems().add(calculateItem);44. final MenuBar menuBar = new MenuBar();45. menuBar.getMenus().setAll(fileMenu, actionMenu);46. calculateButton = new Button();47. calculateButton.setText("Lancer le calcul !");48. calculateButton.setOnAction(new EventHandler<ActionEvent>() {49. 50. @Override51. public void handle(ActionEvent event) {52. doCalculate();53. }54. });55. StackPane center = new StackPane();56. center.getChildren().add(calculateButton);57. final BorderPane root = new BorderPane();58. root.setTop(menuBar);59. root.setCenter(center);60. scene = new Scene(root, 300, 250);61. primaryStage.setTitle("Test");62. primaryStage.setScene(scene);63. primaryStage.show();64. }65. 66. private void doCalculate() {67. final Cursor oldCursor = scene.getCursor();68. scene.setCursor(Cursor.WAIT);69. calculateItem.setDisable(true);70. calculateButton.setDisable(true);71. final int maxIterations = 1000000;72. for (int iterations = 0; iterations < maxIterations; iterations ++) {73. System.out.println(iterations);74. }75. scene.setCursor(oldCursor);76. calculateItem.setDisable(false);77. calculateButton.setDisable(false);78. }79. 80. public static void main(String[] args) {81. launch(args);82. }83. }

Code JDK81. package test;2. 3. import javafx.application.Application;4. import javafx.application.Platform; 5. import javafx.event.ActionEvent;6. import javafx.scene.Cursor;7. import javafx.scene.Scene;8. import javafx.scene.control.Button;9. import javafx.scene.control.Menu;10. import javafx.scene.control.MenuBar;11. import javafx.scene.control.MenuItem;12. import javafx.scene.layout.BorderPane;13. import javafx.scene.layout.StackPane;14. import javafx.stage.Stage;15. 16. public class Main extends Application {17. 18. private MenuItem calculateItem;19. private Button calculateButton;20. private Scene scene;21. 22. @Override23. public void start(Stage primaryStage) {24. final MenuItem exitItem = new MenuItem("Quitter");25. exitItem.setOnAction((ActionEvent t) -> Platform.exit());

Page 5: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 5 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

Code JDK826. final Menu fileMenu = new Menu("Fichier");27. fileMenu.getItems().add(exitItem);28. calculateItem = new MenuItem("Calculer");29. calculateItem.setOnAction((ActionEvent t) -> doCalculate());30. final Menu actionMenu = new Menu("Action");31. actionMenu.getItems().add(calculateItem);32. final MenuBar menuBar = new MenuBar();33. menuBar.getMenus().setAll(fileMenu, actionMenu);34. calculateButton = new Button();35. calculateButton.setText("Lancer le calcul !");36. calculateButton.setOnAction((ActionEvent event) -> doCalculate());37. StackPane center = new StackPane();38. center.getChildren().add(calculateButton);39. final BorderPane root = new BorderPane();40. root.setTop(menuBar);41. root.setCenter(center);42. scene = new Scene(root, 300, 250);43. primaryStage.setTitle("Test");44. primaryStage.setScene(scene);45. primaryStage.show();46. }47. 48. private void doCalculate() {49. final Cursor oldCursor = scene.getCursor();50. scene.setCursor(Cursor.WAIT);51. calculateItem.setDisable(true);52. calculateButton.setDisable(true);53. final int maxIterations = 1000000;54. for (int iterations = 0; iterations < maxIterations; iterations ++) {55. System.out.println(iterations);56. }57. scene.setCursor(oldCursor);58. calculateItem.setDisable(false);59. calculateButton.setDisable(false);60. }61. 62. public static void main(String[] args) {63. launch(args);64. }65. }

Ce qui nous donne une interface similaire à celle-ci :

Figure 1 - L'interface qui va lancer le calcul.

Page 6: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 6 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

Nous pouvons désormais lancer notre calcul soit en cliquant directement sur le bouton Lancer le calcul !, soit enallant dans le menu Action -> Calculer.

Lorsque nous démarrons le calcul, nous pouvons nous rendre compte de plusieurs choses :

• le curseur ne change jamais d'apparence ;• le bouton n'est jamais désactivé ;• l'interface est complètement bloquée : il n'est plus possible d'aller dans les menus par exemple ou même de

fermer la fenêtre normalement.

C'est bien normal : nous réagissons immédiatement à l'action sur le bouton ou le menu et nous exécutons notre longcalcul dans le même thread que celui qui gère l'affichage et les événements dans l'UI. Cette dernière ne peut doncplus rafraichir son affichage ni même réagir à nos saisies.

Et souvenez-vous qu'il s'agit ici d'un simple calcul numérique, le problème se pose également lors de la lecture oul'écriture de fichiers, d'un accès à une base de données, sur le réseau ou sur Internet…

II-B - Raisonnement

Tout comme Swing et AWT s'exécutent dans l'EDT (Event Dispatch Thread), JavaFX utilise des threads qui lui sontpropres tels que le JavaFX Application Thread, le thread de rendu Prism ou encore le thread media. Les événementsreçus via les listeners ou le binding ou encore les appels aux callbacks sont tous exécutés dans le JavaFX ApplicationThread.

Tout comme dans Swing et AWT, lancer un traitement long dans le thread de gestion des événements de JavaFXpeut mener à un blocage (freeze) de l'UI de l'application qui ne répond alors plus aux entrées de l'utilisateur. Cedernier peut alors être amené à penser que l'application est boguée ou plantée. Il faut donc exécuter ce genre detraitement en tâche de fond (background task) sans bloquer les threads de l'UI.

Bien qu'il soit possible de lancer manuellement un ou plusieurs Thread pour effectuer le traitement, la gestion de laremontée d'informations vers l'UI (progression, message, remontée des erreurs) peut s'avérer assez compliquée àprogrammer. C'est ici qu'intervient l'API de concurrence de JavaFX !

II-C - Solution

Reprenons notre code et utilisons l'API de concurrence de JavaFX. Pour cela, nous allons juste modifier le codede la méthode doCalculate() ainsi que les imports pour lister les nouvelles classes nécessaires. Nous reviendronsultérieurement sur les diverses classes et mécanismes introduits dans ce code :

Code JDK71. package test;2. 3. import javafx.application.Application;4. import javafx.application.Platform; 5. import javafx.beans.value.ChangeListener;6. import javafx.beans.value.ObservableValue;7. import javafx.concurrent.Service;8. import javafx.concurrent.Task;9. import javafx.concurrent.Worker;10. import javafx.event.ActionEvent;11. import javafx.event.EventHandler;12. import javafx.scene.Cursor;13. import javafx.scene.Scene;14. import javafx.scene.control.Button;15. import javafx.scene.control.Menu;16. import javafx.scene.control.MenuBar;17. import javafx.scene.control.MenuItem;18. import javafx.scene.layout.BorderPane;19. import javafx.scene.layout.StackPane;

Page 7: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 7 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

Code JDK720. import javafx.stage.Stage;21. 22. public class Main extends Application {23. 24. private MenuItem calculateItem;25. private Button calculateButton;26. private Scene scene;27. 28. @Override29. public void start(Stage primaryStage) {30. final MenuItem exitItem = new MenuItem("Quitter");31. exitItem.setOnAction(new EventHandler<ActionEvent>() {32. 33. @Override34. public void handle(ActionEvent t) {35. Platform.exit();36. }37. }); final Menu fileMenu = new Menu("Fichier");38. fileMenu.getItems().add(exitItem);39. calculateItem = new MenuItem("Calculer");40. calculateItem.setOnAction(new EventHandler<ActionEvent>() {41. 42. @Override43. public void handle(ActionEvent t) {44. doCalculate();45. }46. });47. final Menu actionMenu = new Menu("Action");48. actionMenu.getItems().add(calculateItem);49. final MenuBar menuBar = new MenuBar();50. menuBar.getMenus().setAll(fileMenu, actionMenu);51. calculateButton = new Button();52. calculateButton.setText("Lancer le calcul !");53. calculateButton.setOnAction(new EventHandler<ActionEvent>() {54. 55. @Override56. public void handle(ActionEvent event) {57. doCalculate();58. }59. });60. StackPane center = new StackPane();61. center.getChildren().add(calculateButton);62. final BorderPane root = new BorderPane();63. root.setTop(menuBar);64. root.setCenter(center);65. scene = new Scene(root, 300, 250);66. primaryStage.setTitle("Test");67. primaryStage.setScene(scene);68. primaryStage.show();69. }70. 71. private void doCalculate() {72. final Cursor oldCursor = scene.getCursor();73. scene.setCursor(Cursor.WAIT);74. calculateItem.setDisable(true);75. calculateButton.setDisable(true);76. final Service<Void> calculateService = new Service<Void>() {77. 78. @Override79. protected Task<Void> createTask() {80. return new Task<Void>() {81. 82. @Override83. protected Void call() throws Exception {84. final int maxIterations = 1000000;85. for (int iterations = 0; iterations < maxIterations; iterations ++) {86. System.out.println(iterations);87. }88. return null;89. }

Page 8: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 8 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

Code JDK790. };91. }92. };93. calculateService.stateProperty().addListener(new ChangeListener<Worker.State>() {94. 95. @Override96. public void changed(ObservableValue<? extends Worker.State> observableValue, Worker.State oldValue, Worker.State newValue) {97. switch (newValue) {98. case FAILED:99. case CANCELLED:100. case SUCCEEDED:101. scene.setCursor(oldCursor);102. calculateItem.setDisable(false);103. calculateButton.setDisable(false);104. break;105. }106. }107. });108. calculateService.start();109. }110. 111. public static void main(String[] args) {112. launch(args);113. }114. }

Code JDK1. package test;2. 3. import javafx.application.Application;4. import javafx.application.Platform; 5. import javafx.beans.value.ObservableValue;6. import javafx.concurrent.Service;7. import javafx.concurrent.Task;8. import javafx.concurrent.Worker;9. import javafx.event.ActionEvent;10. import javafx.scene.Cursor;11. import javafx.scene.Scene;12. import javafx.scene.control.Button;13. import javafx.scene.control.Menu;14. import javafx.scene.control.MenuBar;15. import javafx.scene.control.MenuItem;16. import javafx.scene.layout.BorderPane;17. import javafx.scene.layout.StackPane;18. import javafx.stage.Stage;19. 20. public class Main extends Application {21. 22. private MenuItem calculateItem;23. private Button calculateButton;24. private Scene scene;25. 26. @Override27. public void start(Stage primaryStage) {28. final MenuItem exitItem = new MenuItem("Quitter");29. exitItem.setOnAction((ActionEvent t) -> Platform.exit());30. final Menu fileMenu = new Menu("Fichier");31. fileMenu.getItems().add(exitItem);32. calculateItem = new MenuItem("Calculer");33. calculateItem.setOnAction((ActionEvent t) -> doCalculate());34. final Menu actionMenu = new Menu("Action");35. actionMenu.getItems().add(calculateItem);36. final MenuBar menuBar = new MenuBar();37. menuBar.getMenus().setAll(fileMenu, actionMenu);38. calculateButton = new Button();39. calculateButton.setText("Lancer le calcul !");40. calculateButton.setOnAction((ActionEvent event) -> doCalculate());41. StackPane center = new StackPane();42. center.getChildren().add(calculateButton);

Page 9: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 9 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

Code JDK43. final BorderPane root = new BorderPane();44. root.setTop(menuBar);45. root.setCenter(center);46. scene = new Scene(root, 300, 250);47. primaryStage.setTitle("Test");48. primaryStage.setScene(scene);49. primaryStage.show();50. }51. 52. private void doCalculate() {53. final Cursor oldCursor = scene.getCursor();54. scene.setCursor(Cursor.WAIT);55. calculateItem.setDisable(true);56. calculateButton.setDisable(true);57. final Service<Void> calculateService = new Service<Void>() {58. 59. @Override60. protected Task<Void> createTask() {61. return new Task<Void>() {62. 63. @Override64. protected Void call() throws Exception {65. final int maxIterations = 1000000;66. for (int iterations = 0; iterations < maxIterations; iterations ++) {67. System.out.println(iterations);68. }69. return null;70. }71. };72. }73. };74. calculateService.stateProperty().addListener((ObservableValue<? extends Worker.State> observableValue, Worker.State oldValue, Worker.State newValue) -> {75. switch (newValue) {76. case FAILED:77. case CANCELLED:78. case SUCCEEDED:79. scene.setCursor(oldCursor);80. calculateItem.setDisable(false);81. calculateButton.setDisable(false);82. break;83. }84. });85. calculateService.start();86. }87. 88. public static void main(String[] args) {89. launch(args);90. }91. }

Désormais, notre programme se comporte comme prévu :

• le curseur change d'apparence au début et à la fin du calcul ;• le bouton, ainsi que l'entrée dans le menu, est désactivé au début du calcul et réactivé lorsque ce dernier se

termine ;• l'interface n'est plus bloquée : nous pouvons naviguer dans les menus et nous pouvons fermer la fenêtre

normalement.

Nous pouvons également voir que nous n'avons pas eu à créer un nouveau thread manuellement ni d'avoir ànous synchroniser dessus. Cependant le calcul s'est bien déroulé dans un autre fil d'exécution différent du JavaFXApplication Thread.

Page 10: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 10 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

III - Interfaces et classes importantes

III-A - javafx.concurrent.Worker<V>

Cette interface est l'entité parente des classes concrètes qui permettent de lancer une tâche de longue durée enfond de l‘UI. Vous ne serez pas amené à la manipuler directement, cependant vous pouvez remarquer la présencede plusieurs méthodes retournant des propriétés telles que progress, message, title, etc. qui permettent de suivrede la tâche depuis l'UI.

Le type V passé en paramètre de Worker est tout simplement le type de la valeur qui sera retournée par la méthodecall(). C'est-à-dire le type du résultat de la tâche, car cette méthode est celle qui sera appelée pour effectuer latâche de longue durée. Généralement, pour des tâches ne retournant pas de valeur, V sera soit non déclaré (et doncimplicitement de type Object) ou de type Void avec une valeur de retour à null dans les deux cas.

Note : java.lang.Void est un type spécial de la JVM dont la seule valeur possible est null.

Un Worker dispose également d'une propriété état, state, qui changera de valeur au cours de la vie de la tâche. Lesvaleurs possibles de cet état sont définies dans l'enum Worker.State :

• READY - la tâche est prête à être exécutée ; c'est l'état initial ;• SCHEDULED - la tâche est prévue pour être exécutée ; il s'agit d'un état intermédiaire entre READY et

RUNNING ;• RUNNING - la tâche démarre son exécution ;• FAILED - la tâche a échoué, généralement à cause d'une exception qui a été levée au cours du traitement ;• CANCELLED - la tâche a été annulée, généralement par l'utilisateur depuis l'UI ;• SUCCEEDED - la tâche s'est correctement exécutée et a retourné son résultat.

Cette interface est implémentée par trois classes abstraites :

• Service<V> : une classe qui est instanciée et manipulée par l'UI pour créer, gérer et superviser la tâche delongue durée ;

• ScheduledService<V> : une classe qui est instanciée et manipulée par l'UI pour créer, gérer et superviser latâche de longue durée qui peut se répéter à intervalles réguliers. JDK8 ou ultérieur ;

• Task<V> : une classe qui est instanciée et manipulée par le service ou le service répétable pour exécuter latâche de longue durée dans un thread séparé.

Figure 2 - Hiérarchie des classes.

III-B - javafx.concurrent.Service<V>

La classe Service est destinée à être utilisée et manipulée dans l'UI. Son seul vrai but est de créer une tâche (Task)qui sera exécutée en arrière-plan dans un autre thread. Une instance de Service est destinée à être réutilisable,vous pouvez donc conserver une référence sur une instance de Service et l'exécuter plusieurs fois d'affilée.

Pour instancier un service, il suffit de créer une nouvelle classe anonyme qui étend Service<V> et de surcharger laméthode abstraite createTask() pour qu'elle crée une Task<V> du même type que le service :

1. Service<Image> imageLoadingService = new Service<Image>(){2. 3. @Override4. protected Task<Image> createTask() {5. // Instancier et retourner une Task<Image> ici.

Page 11: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 11 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

6. } 7. };

On peut aussi étendre Service<V> dans une classe à part :

1. public class ImageLoadingService extends Service<Image> {2. 3. @Override4. protected Task<Image> createTask() {5. // Instancier et retourner une Task<Image> ici.6. } 7. };

III-C - javafx.concurrent.Task<V>

C'est cette classe qui sera amenée à faire le calcul, la requête ou le traitement de longue durée et c'est dans laméthode call() de cette classe que la tâche est effectuée ; cette méthode sera appelée dans un thread séparé,différent du JavaFX Application Thread.

Contrairement à Service, une Task est destinée à n'être exécutée qu'une seule et unique fois. Une tâche n'est pasréutilisable !

Pour créer une tâche, il suffit de créer une nouvelle classe anonyme qui étend Task<V> et de surcharger la méthodecall() pour qu'elle renvoie une instance du type approprié :

1. Task<Image> imageLoadingTask = new Task<Image>(){2. 3. @Override4. protected Image call() throws Exception {5. // Charger l'image ici.6. Image image = ...7. return image;8. }9. };

On peut aussi étendre Task<V> dans une classe à part :

1. public class ImageLoadingTask extends Task<Image> {2. 3. @Override4. protected Image call() throws Exception {5. // Charger l'image ici.6. Image image = ...7. return image;8. }9. }

En plus des calculs, accès IO, requête web ou BD ou autre, vous pouvez faire tout ce que vous faites habituellementdans un thread au cours de ce traitement : boucle infinie, appel à Thread.sleep(), etc. sans pour autant que celane bloque l'UI.

Attention cependant, toute exception qui n'est pas catchée dans ce traitement remontera au niveau supérieur etpassera automatiquement la tâche en l'état FAILED en plus d'arrêter le calcul ou le traitement en cours.

III-D - Javafx.concurrent.ScheduledService<V>

La classe ScheduledService, introduite dans le JDK8 permet de créer des services qui se répètent à intervallesréguliers. Elle fonctionne comme la class Service mais dispose de propriétés supplémentaires pour gérer la répétitionainsi que les conditions de gestion des échecs.

Page 12: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 12 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

Un ScheduledService, se relance automatiquement lorsque son exécution précédente s'est terminée correctementune fois le temps d'attente spécifié par le programmeur écoulé. Il peut également se relancer en cas d'erreurd'exécution suivant les conditions de gestion des échecs qui ont été spécifiées.

IV - Utiliser un service

Vous serez donc amené à utiliser la classe Service depuis le code de votre UI, en réaction à un clic sur un bouton, dansun menu, une sélection dans une table, une liste ou un menu déroulant, etc. La majorité de ces actions s'exécuteradonc dans le JavaFX Application Thread. Voyons donc maintenant comment utiliser un service.

IV-A - Création et lancement

Reprenons notre code qui permet d'initialiser notre service et complétons-le avec le code qui initialise la tâche :

1. Service<Image> imageLoadingService = new Service<Image>(){2. 3. @Override4. protected Task<Image> createTask() {5. return new Task<Image>(){6. 7. @Override8. protected Image call() throws Exception {9. // Charger l'image ici.10. Image image = ...11. return image;12. }13. };14. }15. };16. imageLoadingService.start();

Ou, pour une tâche qui ne retourne rien :

1. Service<Void> fileSaveService = new Service<Void>(){2. 3. @Override4. protected Task<Void> createTask() {5. return new Task<Void>(){6. 7. @Override8. protected Void call() throws Exception {9. // Sauvegarder le fichier ici.10. [...]11. return null;12. }13. };14. }15. };16. fileSaveService.start();

L'appel à la méthode start() du service, provoquera le démarrage de la tâche de fond dans un nouveau thread.

IV-B - Annulation

Pour annuler un service en cours d'exécution, il suffit d'appeler la méthode cancel() du service depuis l'UI (parexemple, un bouton). Lorsque la méthode cancel() est appelée, l'état du service et de la tâche passe à CANCELLED.

Attention, cela n'aura pas pour effet d'arrêter la tâche immédiatement ! Comme nous le verrons ultérieurement,vous devrez prendre des précautions supplémentaires dans la tâche pour vous assurer qu'elle termine bien sonexécution.

Page 13: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 13 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

1. imageLoadingService.cancel();

IV-C - Relancer un service

Pour qu'un service puisse être relancé, il doit être dans l'état READY, faute de quoi tout appel à la méthode start() sesoldera par la levée d'une exception de type IllegalStateException. Il convient donc d'appeler sa méthode reset() avantd'appeler la méthode start(). À l'exécution, une nouvelle instance de Task sera alors créée et exécutée par le service.

1. imageLoadingService.reset();2. imageLoadingService.start();

Il est également possible d'appeler la méthode restart() qui fera exactement la même chose que le couple reset()+ start() :

1. imageLoadingService.restart();

IV-D - Avancement du service

Il est possible de binder les propriétés retournées par progressProperty(), messageProperty(), titleProperty(), etc.d'un service vers des éléments de l'UI tels que des instances de Label, ProgressBar ou ProgressIndicator de manièreà suivre l'avancement de l'exécution et les changements d'état de la tâche de fond.

1. Stage dialog = ...2. ProgressIndicator progressIndicator = ...3. Label informationLabel = ...4. [...]5. Service<Image> imageLoadingService = new Service<Image>() {6. [...]7. };8. dialog.titleProperty().bind(imageLoadingTask.titleProperty());9. progressIndicator.progressProperty.bind(imageLoadingTask.progressProperty()) ;10. informationLabel.textProperty().bind(imageLoadingTask.messageProperty());11. imageLoadingService.start();

Il est aussi possible de mettre des ChangeListener sur chacune de ces propriétés pour transmettre leurs changementsde valeur sur d'autres éléments de l'UI.

IV-E - État du service

En général vous aurez envie de superviser les changements d'état d'un service de manière à afficher des messagesd'erreur ou de succès d'opération dans votre UI.

Figure 3 - Les divers états d'un service.

Pour superviser les changements d'état d'une tâche de fond, vous avez deux possibilités.

Page 14: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 14 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

IV-E-1 - ChangeListener sur l'état du service

Vous pouvez mettre un écouteur de type ChangeListener sur la propriété state du service et superviser leschangements de valeur de cette propriété :

Code JDK71. Service<Image> imageLoadingService = new Service<Image>() {2. [...]3. };4. imageLoadingService.stateProperty().addListener(new ChangeListener<Worker.State>() {5. @Override6. public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) {7. switch (newValue) {8. case READY:9. // La tâche est prête. 10. [...]11. break;12. case SCHEDULED:13. // La tâche va être exécutée. 14. [...]15. break;16. case RUNNING:17. // La tâche est en cours d'exécution. 18. [...]19. break;20. case FAILED:21. // La tâche a échoué. 22. [...]23. break;24. case CANCELLED:25. // La tâche a été annulée. 26. [...]27. break;28. case SUCCEEDED:29. // La tâche s'est correctement terminée. 30. [...]31. break;32. }33. }34. });35. imageLoadingService.start();

Code JDK81. Service<Image> imageLoadingService = new Service<Image>() {2. [...]3. };4. imageLoadingService.stateProperty().addListener((ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) -> {5. switch (newValue) {6. case READY:7. // La tâche est prête. 8. [...]9. break;10. case SCHEDULED:11. // La tâche va être exécutée. 12. [...]13. break;14. case RUNNING:15. // La tâche est en cours d'exécution. 16. [...]17. break;18. case FAILED:19. // La tâche a échoué. 20. [...]21. break;22. case CANCELLED:23. // La tâche a été annulée.

Page 15: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 15 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

Code JDK824. [...]25. break;26. case SUCCEEDED:27. // La tâche s'est correctement terminée. 28. [...]29. break;30. }31. });32. imageLoadingService.start();

IV-E-2 - Callback sur le service

Mais l'API vous permet également de mettre des callbacks en place, par exemple :

Code JDK71. Service<Image> imageLoadingService = new Service<Image>() {2. [...]3. };4. imageLoadingService.setOnSucceeded(new EventHandler<WorkerStateEvent>(){5. 6. @Override7. public void handle(WorkerStateEvent event) {8. // La tâche s'est correctement terminée.9. [...]10. }11. });12. imageLoadingService.setOnFailed(new EventHandler<WorkerStateEvent>(){13. 14. @Override15. public void handle(WorkerStateEvent event) {16. // La tâche a échoué.17. [...]18. }19. }); 20. imageLoadingService.start();

Code JDK81. Service<Image> imageLoadingService = new Service<Image>() {2. [...]3. };4. imageLoadingService.setOnSucceeded((WorkerStateEvent event) -> {5. // La tâche s'est correctement terminée.6. [...]7. });8. imageLoadingService.setOnFailed((WorkerStateEvent event) -> {9. // La tâche a échoué.10. [...]11. }); 12. imageLoadingService.start();

Il existe des callbacks pour tous les états possibles :

• setOnReady() - la tâche est prête ;• setOnScheduled() - la tâche va être exécutée ;• setOnRunning() - la tâche démarre son exécution ;• setOnFailed() - la tâche a échoué ;• setOnCancelled() - la tâche a été annulée ;• setOnSucceded() - la tâche s'est correctement terminée.

Toutes ces méthodes sont appelées depuis le JavaFX Application Thread.

Page 16: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 16 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

IV-F - Récupérer le résultat

Pour récupérer le résultat d'un service, il suffit d'appeler la méthode getValue() du service dans le listener ou lecallback approprié. Ainsi le résultat de la tâche pourra être transmis à l'UI ou à un autre traitement.

Code JDK71. // Le service est final, car il doit être appelé depuis le callback.2. final Service<Image> imageLoadingService = new Service<Image>() {3. [...]4. };5. imageLoadingService.setOnSucceeded(new EventHandler<WorkerStateEvent>(){6. 7. @Override8. public void handle(WorkerStateEvent event) {9. // La tâche s'est correctement terminée.10. final Image image = imageLoadingService.getValue();11. [...]12. }13. });14. imageLoadingService.start();

Code JDK81. // Le service est final, car il doit être appelé depuis le callback.2. final Service<Image> imageLoadingService = new Service<Image>() {3. [...]4. };5. imageLoadingService.setOnSucceeded((WorkerStateEvent event) -> {6. // La tâche s'est correctement terminée.7. final Image image = imageLoadingService.getValue();8. [...]9. });10. imageLoadingService.start();

IV-G - Récupérer la raison de l'échec

Pour récupérer la raison de l'échec d'un service, il suffit d'appeler la méthode getException() du service dans lelistener ou le callback approprié. Cela permettra d'afficher l'erreur à l'utilisateur ou de la stocker dans un journal.

Code JDK71. // Le service est final, car il doit être appelé depuis le callback.2. final Service<Image> imageLoadingService = new Service<Image>() {3. [...]4. };5. imageLoadingService.setOnFailed(new EventHandler<WorkerStateEvent>(){6. 7. @Override8. public void handle(WorkerStateEvent event) {9. // La tâche a échoué.10. final Throwable error = imageLoadingService.getException();11. [...]12. }13. });14. imageLoadingService.start();

Code JDK81. // Le service est final, car il doit être appelé depuis le callback.2. final Service<Image> imageLoadingService = new Service<Image>() {3. [...]4. };5. imageLoadingService.setOnFailed((WorkerStateEvent event) -> {6. // La tâche a échoué.7. final Throwable error = imageLoadingService.getException();8. [...]9. });

Page 17: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 17 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

Code JDK810. imageLoadingService.start();

V - Utiliser une tâche

De manière générale, la classe Task sera uniquement manipulée par votre service et la majorité de son codes'exécutera dans son propre Thread, indépendant du JavaFX Application Thread. Voyons donc maintenant commentutiliser une tâche.

V-A - Notification de l'avancement

Depuis la méthode call() de la tâche, il est possible d'envoyer des informations vers l'UI via les méthodesupdateProgress(), updateMessage(), etc. Les valeurs passées à ces méthodes seront transmises vers le JavaFXApplication Thread.

1. return new Task<Integer>() {2. @Override 3. protected Integer call() throws Exception {4. final int maxIterations = 1000000;5. int iterations = 0;6. for (iterations = 0; iterations < maxIterations; iterations++) {7. // On envoie un nouveau message.8. updateMessage("iteration "+ (iterations+1));9. // On change l'avancement de la tâche.10. updateProgress(iterations, maxIterations);11. [...]12. }13. return iterations;14. }15. updateMessage("Done");16. updateProgress(iterations, maxIterations);17. };

Si trop d'appels à ces méthodes ont lieu dans un court laps de temps, il y aura agrégation des différents appels etseules les dernières valeurs seront transmises à l'UI. Ainsi certains messages peuvent être perdus et la progressionpeut sauter des valeurs si elle est trop rapide.

Dans le cas de boucles ou de progressions obtenues par calcul, il faut également faire attention au fait queupdateProgress() générera une exception de type IllegalArgumentException si la valeur d'avancement devientstrictement supérieure à la valeur maximum attendue.

V-B - Annulation

Comme nous l'avons vu précédemment, appeler la méthode cancel() du service met la tâche dans l'état CANCELLED,mais cela ne suffit pas en soi pour arrêter l'exécution de la tâche. Dans votre traitement, vous devez vérifier àintervalles réguliers la valeur retournée par la méthode isCancelled() de la tâche et interrompre le calcul en coursle cas échéant . Par exemple, la tâche suivante effectue 1 000 000 itérations et vérifie si la tâche a été annulée àchaque itération, interrompant la boucle si c'est le cas.

1. return new Task<Integer>() {2. @Override 3. protected Integer call() throws Exception {4. final int maxIterations = 1000000;5. int iterations = 0;6. for (iterations = 0; iterations < maxIterations; iterations++) {7. // Arrêt de la boucle si la tâche est annulée.8. if (isCancelled()) {9. break;10. }11. // On envoie un nouveau message.

Page 18: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 18 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

12. updateMessage("iteration "+ (iterations+1));13. // On change l'avancement de la tâche.14. updateProgress(iterations, maxIterations);15. [...]16. }17. return iterations;18. }19. };

V-B-1 - Lors d'un appel bloquant

Lorsque l'on effectue un appel bloquant, par exemple en mettant le thread courant en pause dans une boucle, il estimportant de vérifier l'état de la tâche une fois que le thread courant est à nouveau actif. En effet, le thread peut seréveiller du fait que la tâche est passée dans l'état CANCELLED.

1. return new Task<Integer>() {2. @Override 3. protected Integer call() throws Exception {4. final int maxIterations = 1000000;5. int iterations = 0;6. for (iterations = 0; iterations < maxIterations; iterations++) {7. // Arrêt de la boucle si la tâche est annulée.8. if (isCancelled()) {9. break;10. }11. // On envoie un nouveau message.12. updateMessage("iteration "+ (iterations+1));13. // On change l'avancement de la tâche.14. updateProgress(iterations, maxIterations);15. [...]16. try {17. Thread.sleep(100);18. } catch (InterruptedException ie) {19. if (isCancelled()) {20. break;21. }22. }23. }24. return iterations;25. }26. };

V-C - État d'une tâche

Il est également possible de superviser les changements d'état d'une tâche. À cet effet, il est possible de surchargerles méthodes scheduled(), running(), failed(), cancelled(), et succeeded() de la classe Task.

• scheduled() - la tâche va être exécutée.• running() - la tâche démarre son exécution.• failed() - la tâche a échoué.• cancelled() - la tâche a été annulée.• succeeded() - la tâche s'est correctement terminée.

Il n'existe pas de méthode pour l'état READY, car il s'agit de l'état par défaut d'une tâche à sa création.

Page 19: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 19 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

Figure 4 - Les divers états d'une tâche.

Toutes ces méthodes sont appelées depuis le JavaFX Application Thread une fois que les propagations aux listenersont été terminées.

V-D - Produire des résultats partiels

V-D-1 - JDK 7

Dans le JDK7, Task ne produit qu'un seul et unique résultat : son résultat final. Cependant, il est assez facile d'ajouterla capacité de produire des résultats partiels en utilisant des propriétés JavaFX sur la tâche.

1. public class IterationsTask extends Task<Integer> {2. 3. private final IntegerProperty partialResult = new SimpleIntegerProperty(this, "partialResult", -1);4. 5. public final int getPartialResults() {6. return partialResult.get();7. }8. 9. public final ReadOnlyIntegerProperty partialResultProperty() {10. return partialResult;11. }12. 13. [...]14. }

Il faudra cependant, dans la méthode call(), prendre des précautions pour que les valeurs soient settées dans leJavaFX Application Thread et non pas dans le thread de la tâche. En effet, généralement des résultats partiels nesont intéressants que dans le cadre d'un affichage dans une UI. Or, une fois affichées dans une scène, les propriétésdes contrôles JavaFX doivent être uniquement accédées via le JavaFX Application Thread.

1. @Override2. protected Integer call() throws Exception {3. final int maxIterations = 1000000;4. int iterations = 0;5. for (iterations = 0; iterations < maxIterations; iterations++) {6. // Arrêt de la boucle si la tâche est annulée.7. if (isCancelled()) {8. break;9. }10. // ! La propriété doit être manipulée dans la JavaFX Application Thread !11. final int it = iterations;12. Platform.runLater(new Runnable() {13. @Override14. public void run() {15. partialResult.set(it);16. }17. });18. }

Page 20: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 20 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

19. return iterations;20. }

Il faut faire également attention au fait qu'ici, il n'y a pas de fusion ou agrégation des envois de valeurs partiellessur le JavaFX Application Tread. Cela peut donc provoquer une monopolisation ou une surcharge du thread par latâche à cause du trop grand nombre de requêtes à Platform.runLater() et nous ramener à notre problème de blocageinitial. Ici, cet exemple ne fait pas moins de 1 000 000 de requêtes à Platform.runLater() dans une boucle rapidesans aucune pause ! Cela peut faire que l'application cesse de répondre, ne puisse plus se redimensionner ou plantecarrément, bref on perdrait tous les avantages liés à l'utilisation d'un service !

Il faudra donc penser à aménager des temps de repos en appelant Thread.sleep() à intervalles réguliers de manièreà ce que le JavaFX Application Thread puisse continuer à s'exécuter et évacuer les événements en attente.

1. @Override2. protected Integer call() throws Exception {3. final int maxIterations = 1000000;4. int iterations = 0;5. for (iterations = 0; iterations < maxIterations; iterations++) {6. // Arrêt de la boucle si la tâche est annulée.7. if (isCancelled()) {8. break;9. }10. // ! La propriété doit être manipulée dans la JavaFX Application Thread !11. final int it = iterations;12. Platform.runLater(new Runnable() {13. @Override14. public void run() {15. partialResult.set(it);16. }17. });18. // Interragir ainsi avec le JavaFX Application Thread demande de lui laisser un peu le temps de respirer.19. try {20. Thread.sleep(50);21. } catch (InterruptedException ie) {22. if (isCancelled()) {23. break;24. }25. }26. }27. return iterations;28. }

On pourrait également modifier le code pour faire en sorte que les mises à jour de l'UI arrivent tous les 1000 élémentspar exemple, de manière à réduire le nombre de mises à jour effectuées.

V-D-2 - JDK 8

Le JDK 8 a ajouté la possibilité d'invoquer la méthode updateValue() dans le corps de la tâche pour mettre à jourle contenu de la propriété value de l'objet Task (et de son service parent). Il suffira alors de placer un écouteur detype InvalidationListener ou ChangeListener sur cette propriété pour être mis au courant de ses changements devaleur au cours du calcul.

La méthode updateValue() peut être invoquée depuis n'importe quel thread même celui dans lequel s'exécute latâche. Tout comme les autres méthodes updateXXX(), les appels sont fusionnés lorsqu'ils sont trop nombreux, cequi fait que certains changements de valeur peuvent être omis.

Code JDK8 @Override protected Integer call() throws Exception { final int maxIterations = 1000000;

Page 21: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 21 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

Code JDK8 int iterations = 0; for (iterations = 0; iterations < maxIterations; iterations++) { // Arrêt de la boucle si la tâche est annulée. if (isCancelled()) { break; } // ! La propriété doit être manipulée dans la JavaFX Application Thread ! final int it = iterations; updateValue(it); } } return iterations; }

V-E - Utiliser une tâche dans un contexte non JavaFX

Vous pouvez être amené à utiliser une tâche dans un contexte non JavaFX, par exemple, dans le but d'effectuer destests unitaires. De plus, la classe Task<V> hérite des interfaces Runnable et RunnableFuture<V>, il est donc tout àfait possible de la passer à un Thread Java normal pour exécution :

1. final Thread imageLoadingThread = new Thread(imageLoadingTask);2. imageLoadingThread.start();

Il est également possible de passer une tâche à un ExecutorService :

1. anExecutorService.submit(imageLoadingTask);

Cependant, vous serez rarement amené à utiliser ces cas de figure.

Attention, si vous utilisez Task dans un contexte où le JavaFX Application Thread n'a pas été initialisé, il ne fautpas appeler updateMessage() ou updateProgress() ou n'importe quelle autre méthode updateXXX() sous peine degénérer des exceptions. De plus, cette approche peut ne pas fonctionner non plus avec des tâches produisantdes résultats partiels telles que celles que nous avons montrées pour le JDK 7 puisqu'elles invoquent la méthodePlatform.runLater() qui suppose que les runtimes JavaFX ont été correctement initialisés.

VI - Utiliser un service répétable

La classe ScheduledService, introduite dans le JDK8, permet de créer un service qui s'exécute de manière cyclique.Cette classe s'utilise exactement comme la classe Service, mais introduit de nouveaux concepts et propriétés.Lorsque le service s'exécute correctement, il redémarre automatiquement après un temps d'attente. Au contraire,lorsque le service échoue, le programmeur peut décider s'il se relance en fonction de plusieurs critères. Étant donnéque le service se répète automatiquement de manière cyclique, il repasse régulièrement par tous les états possiblesau cours de son exécution.

Attention, la gestion des durées par cette classe est indicative ; il ne faut pas utiliser ce genre de service pour destâches demandant une précision extrême dans la gestion du temps.

VI-A - Délai avant l'exécution

Tout d'abord, la propriété delay permet de spécifier un temps d'attente avant le démarrage initial du service.

1. service.setDelay(Duration.seconds(5));

Page 22: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 22 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

VI-B - Période d'exécution

La propriété period permet de définir le temps d'attente entre deux exécutions sans erreur. C'est-à-dire le temps quis'écoule entre chaque moment dans lequel l'état du service entre en l'état RUNNING.

1. service.setPeriod(Duration.seconds(1));

Si la tâche prend plus de temps à s'exécuter que la durée spécifiée dans la période, ou si la période est vide (ex. :Duration.ZERO ou une durée indéterminée), le service se relance alors immédiatement après la fin de la tâche sansplus attendre. De plus, le service ne peut interrompre une tâche en cours si elle dépasse le temps d'attente indiquépour la période.

VI-C - Gestion des échecs

Par défaut, en cas d'échec, le service se termine immédiatement. Il est possible de changer ce comportement enmodifiant la valeur de la propriété restartOnFailure pour indiquer si le service doit se relancer en cas d'échec. Il estégalement possible de modifier la valeur de la propriété maxFailureCount pour indiquer le nombre maximal d'échecssupportés. La propriété en lecture seule currentFailureCount permet de connaitre le nombre d'échecs encourus parle service jusqu'à présent.

1. service.setRestartOnFailure(true); // On continue l'exécution en cas d'erreur.2. service.setMaxFailureCount(100); // Et au maximum on acceptera 100 échecs.

La propriété backoffStrategy permet de spécifier une fabrique à durées de type Callback<ScheduledService<?>,Duration> qui sera invoquée lorsque le service a échoué. Cela permet par exemple, d'augmenter, le temps d'attenteentre chaque échec successif. Cette valeur sera ajoutée à la période du service lors du temps d'attente avant laprochaine tentative d'exécution. Cette valeur peut être plafonnée en utilisant la propriété maximumCumulativePeriod.

1. service.setBackoffStrategy(service -> Duration.seconds(service.getCurrentFailureCount() * 5)); // On ajoute 5 secondes d'attente entre chaque échec.2. service.setMaximumCumulativePeriod(Duration.minutes(10)); // Au maximum on attendra 10 minutes avant la prochaine tentative.

VI-D - Derniers bons résultats

La valeur de la propriété value du service sera réinitialisée à null avant chaque fois que le service se relance.

Il est donc plus intéressant de se pencher sur la propriété en lecture seule lastValue, qui contient la dernière valeurgénérée lors d'une exécution correcte de la tâche. Elle est initialement à la valeur null et ne sera mise à jour quelorsque la tâche s'exécute correctement. Il est donc possible de suivre ses changements de valeur en lui ajoutant unécouteur de type InvalidationListener ou ChangeListener.

VII - Conclusion

Vous savez désormais comment lancer une tâche de fond en JavaFX, ceci vous permettra de lancer tous vostraitements longs en arrière-plan de l'UI sans pour autant geler votre interface graphique.

VIII - Remerciements

Je tiens à remercier toute l'équipe du forum Développez ainsi que Mickael Baron et Gueritarish pourleurs suggestions et leurs relectures du présent article. Je tiens également à remercier Claude Leloup pour sescorrections orthographiques.

Page 23: Javafx Service (Thread)

Tutoriel sur l'exécution d'une tâche de fond en JavaFX par Fabrice Bouyé

- 23 -Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de

présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Fabrice Bouyé. Aucune reproduction,même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisationexpresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

http://fabrice-bouye.developpez.com/tutoriels/javafx/gui-service-tache-de-fond-thread-javafx/

IX - Liens

• Page javadoc de l'interface Worker<V>.• Page javadoc de la classe Service<V>.• Page javadoc de la classe ScheduledService<V>.• Page javadoc de la classe Task<V>.