Bonnes pratiques pour exécuter une tâche de fond en JavaFX,
Un tutoriel de Fabrice Bouyé
Le 2015-02-20 01:44:54, par bouye, Rédacteur/Modérateur
Bonjour,
je vous propose un nouvel article concernant JavaFX, cette fois-ci sur les bonnes pratiques pour exécuter une tâche de fond, de longue durée ou récurrente sans pour autant bloquer votre interface graphique :
http://fabrice-bouye.developpez.com/...thread-javafx/
Vous pouvez profiter de ce message pour partager vos commentaires. Cet article a été commencé sous JavaFX 2.2 il y a plus d'un an et j'ai ensuite mis à jour certaines parties pour JavaFX 8 qui a entre autres rajouté une classe permettant d’effectuer des tâches récurrentes, donc, surtout, n’hésitez pas à m'indiquer toutes erreurs, omissions ou encore des éventuels anomalies.
je vous propose un nouvel article concernant JavaFX, cette fois-ci sur les bonnes pratiques pour exécuter une tâche de fond, de longue durée ou récurrente sans pour autant bloquer votre interface graphique :
http://fabrice-bouye.developpez.com/...thread-javafx/
Vous pouvez profiter de ce message pour partager vos commentaires. Cet article a été commencé sous JavaFX 2.2 il y a plus d'un an et j'ai ensuite mis à jour certaines parties pour JavaFX 8 qui a entre autres rajouté une classe permettant d’effectuer des tâches récurrentes, donc, surtout, n’hésitez pas à m'indiquer toutes erreurs, omissions ou encore des éventuels anomalies.
-
PhosteNouveau membre du ClubBonjour, j'ai lu ton tutoriel sur les Service et les Task mais j'ai tout de même un problème avec l'un des mes programmes ...
J'obtiens le message d'erreur suivant : Exception in thread "MQTT Call: ID1" java.lang.IllegalStateException: Not on FX application thread; currentThread = MQTT Call: ID1
Je créé pourtant un Service qui établi la connection avec la base de donnée MQTT et qui souscrit à une valeur de la base de donnée mqtt. Voici la partie de code qui pose problème :Code : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64import javafx.concurrent.Service; import javafx.concurrent.Task; import javafx.scene.image.ImageView; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; public class Led extends ImageView implements MqttCallback { private static final String BROKER = "tcp://255.255.255.255:1883"; // adresse ip du serveur mqtt private final String name; private final String id; private static int idCount = 1; public Led(String name) { this.name = name.toUpperCase(); this.id = "IDLED"+idCount++; this.getStyleClass().add("led_on"); Service<Void> connectionMqtt = new Service<Void>() { @Override protected Task<Void> createTask() { return new Task<Void>() { @Override protected Void call() throws Exception { try { MqttClient client = new MqttClient(BROKER, Led.this.id); client.setCallback(Led.this); client.connect(); client.subscribe("A000009/"+Led.this.name); } catch(MqttException e) { e.printStackTrace(); } return null; } }; } }; connectionMqtt.start(); } @Override public void connectionLost(Throwable thrwbl) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public void messageArrived(String string, MqttMessage mm) throws Exception { if (mm.toString().equals("1")) this.getStyleClass().setAll("led_on"); else this.getStyleClass().setAll("led_off"); } @Override public void deliveryComplete(IMqttDeliveryToken imdt) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }
Code : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40import javafx.scene.control.Button; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; public class Switch extends Button { private static final String BROKER = "tcp://255.255.255.255:1883"; private static final int QOS = 2; private String name; private String status = "ON"; private final String id; private static int idCount = 1; private MqttClient client; public Switch(String name) { this.name = name.toUpperCase(); this.id = "IDSWI"+idCount++; try { client = new MqttClient(BROKER, this.id); } catch(MqttException e) { e.printStackTrace(); } } public void publish() { try { client.connect(); MqttMessage message = new MqttMessage(status.getBytes()); status = (status.equals("ON")) ? "OFF" : "ON"; message.setQos(QOS); client.publish("A000009/"+this.name, message); client.disconnect(); } catch (MqttException e) { e.printStackTrace(); } } }
Merci d'avancele 01/03/2015 à 23:21 -
bouyeRédacteur/ModérateurPour rappel, le corps de la Task s’exécute dans un autre thread.
D’après ton erreur, ici tu appelles, depuis un autre thread, quelques chose qui devrait en fait s'effectuer dans le JavaFX Application Thread.
Donc je suppose, a priori, qu'il s'agit de quelque chose que tu appelles dans ta tache (puisque tu n'as pas donne de trace ou de numéro de ligne pour cette erreur) :Code : 1
2
3
4MqttClient client = new MqttClient(BROKER, Led.this.id); client.setCallback(Led.this); client.connect(); client.subscribe("A000009/"+Led.this.name);
Ça serait plus facile si je savais quelle ligne provoque l'erreurle 02/03/2015 à 4:14 -
PhosteNouveau membre du ClubAlors, niveau des erreurs j'obtiens ceci :
Code : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37Exception in thread "MQTT Call: IDLED1" java.lang.IllegalStateException: Not on FX application thread; currentThread = MQTT Call: IDLED1 at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:204) at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:364) at javafx.scene.Scene.addToDirtyList(Scene.java:485) at javafx.scene.Node.addToSceneDirtyList(Node.java:424) at javafx.scene.Node.impl_markDirty(Node.java:415) at javafx.scene.Node.impl_geomChanged(Node.java:3784) at javafx.scene.image.ImageView.access$300(ImageView.java:143) at javafx.scene.image.ImageView$1.invalidated(ImageView.java:223) at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:111) at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:145) at javafx.scene.image.ImageView.setImage(ImageView.java:189) at javafx.scene.image.ImageView$2.invalidated(ImageView.java:259) at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:109) at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:143) at javafx.css.StyleableStringProperty.set(StyleableStringProperty.java:83) at javafx.css.StyleableStringProperty.applyStyle(StyleableStringProperty.java:69) at javafx.css.StyleableStringProperty.applyStyle(StyleableStringProperty.java:45) at javafx.scene.CssStyleHelper.resetToInitialValues(CssStyleHelper.java:452) at javafx.scene.CssStyleHelper.createStyleHelper(CssStyleHelper.java:180) at javafx.scene.Node.reapplyCss(Node.java:8785) at javafx.scene.Node.impl_reapplyCSS(Node.java:8748) at javafx.scene.Node$3.onChanged(Node.java:998) at com.sun.javafx.collections.TrackableObservableList.lambda$new$20(TrackableObservableList.java:45) at com.sun.javafx.collections.TrackableObservableList$$Lambda$54/602720129.onChanged(Unknown Source) at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164) at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73) at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233) at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482) at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541) at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205) at javafx.collections.ModifiableObservableListBase.setAll(ModifiableObservableListBase.java:90) at javafx.collections.ObservableListBase.setAll(ObservableListBase.java:250) at simulateur_v2.Led.messageArrived(Led.java:60) at org.eclipse.paho.client.mqttv3.internal.CommsCallback.handleMessage(CommsCallback.java:354) at org.eclipse.paho.client.mqttv3.internal.CommsCallback.run(CommsCallback.java:162) at java.lang.Thread.run(Thread.java:745)
Autre chose... J'ai exécuté le même code sur un PC à mon école et je n'ai eu aucune exception. Est-il possible que cela change d'un environnement à un autre ? (mon netbeans est à la v8.0.1 et celui de l'école à la v8.0)le 02/03/2015 à 18:11 -
bouyeRédacteur/ModérateurLe problème semble avoir donc une cause assez simple : tu as passé ta LED comme callback or cette dernière a reçu un message et a tenté de le traiter dans un thread hors du JavaFX Application Thread. Comme c'est du multithread, l'ordre de séquences d'instructions peut varier suivant plein de facteurs (rapidité/lenteur d’exécution, etc...) donc ça marche d’un coté (école) et pas de l'autre (maison) ; mais le problème persiste : tu as tenté une modification de la scène depuis un thread autre que le JavaFX Application Thread, donc *pouf* ça plante.
Code : at simulateur_v2.Led.messageArrived(Led.java:60)
Code : 1
2
3
4
5
6
7
8
9@Override public void messageArrived(String string, MqttMessage mm) throws Exception { if (!Platform.isFxApplicationThread()) { Platform.runLater(() -> messageArrived(mm)); } else { String ledStyle = (mm.toString().equals("1")) ? "led_on" : "led_off"; this.getStyleClass().setAll(ledStyle); } }
le 02/03/2015 à 23:01 -
PhosteNouveau membre du ClubEn fait ça ne fonctionnait pas mieux à mon école, je n'avais juste pas d'exception qui avait été levée donc aucun message ...
Ça marche avec ta solutionmerci !
Je n'ai même pas besoin de me connecter à la db sur un autre thread (il le fait peut-être seul ?)le 03/03/2015 à 11:56 -
bouyeRédacteur/ModérateurFaut voir avec ta méthode de connexion sur la DB. Sur une DB distante avec une connexion réseau lente, un serveur surchargé ou avec une authentification lente, ça pourrait éventuellement prendre un temps non négligeable et donc ça bloquerait ton UI si tu ne passait pas par un service. Ici, on prend tout le temps qu'il faut dans la tache et seul le changement d'état de la LED a lieu dans le JavaFX Application Thread, donc c'est pile poil.le 03/03/2015 à 20:54
-
foxfxNouveau Candidat au ClubJ'ai tout l'article, il est clair et les exemples sont aident à la compréhension des API de concurrence de JavaFX merci pour cet article !
J'ai juste une remarque sur la récupération de la raison de l'erreur, je pense qu'il s'agit de :
Pour récupérer la raison de l'échec d'un service, il suffit d'appeler la méthode getException() du service dans le listener ou le callback approprié.
à la place de
Pour récupérer la raison de l'échec d'un service, il suffit d'appeler la méthode getError() du service dans le listener ou le callback approprié.le 23/03/2015 à 17:50 -
bouyeRédacteur/ModérateurMerci, ça aura échappé aux multiples relectures (/doh). Je corrige de suite.le 23/03/2015 à 22:13
-
GouyonMembre expérimentéTrès bon tutoriel
Par contre j'ai un cas qui fonctionne très mal.
Le code ci dessous charge un scénario pour un jeu et prépare l'affichage. Le chargement dure environ une quinzaine de seconde ce qui est long et peut faire penser que le programme est planté.Code : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public void chargerJeu(String Nom) { try { FileInputStream fSvg = new FileInputStream(Nom); ObjectInputStream svgBat = new ObjectInputStream(fSvg); scenario = (Jeu) svgBat.readObject(); prepareScenario(); fondDeCarte = new Visu(scenario.getLeTerrain(), scenario.getFeux()); vueCarte.setImage(fondDeCarte.getImage()); int fig = 0; Pion unite = scenario.getPion(fig++); while (unite != null) { if (!unite.estAlaBase()) affiche(unite); if (unite instanceof Vehicule) { ((Vehicule) unite).majCarc(paramJeu); if (!unite.estAlaBase()) doCalcNouvGraphePour((Vehicule) unite, unite.getX(), unite.getY()); } unite = scenario.getPion(fig++); } centreEnXY(0, 0); changeZoom(1); corrigeLocation(0, 0); jgeCtrl.MAJ(); jgeCtrl.razMessage(); jgeCtrl.setUniteEnMain("", "", null); cmdCtrl.setMiniCarte(fondDeCarte.getImage()); cmdCtrl.enableAll(true); cmdCtrl.MAJCmd(); } catch (IOException | ClassNotFoundException e1) { e1.printStackTrace(); } }
Code : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83private void doLoadGame(String Nom) { final Cursor oldCursor = primaryScene.getCursor(); primaryScene.setCursor(Cursor.WAIT); final Stage dialStage = new Stage(); final Service<Void> loadGameService = new Service<Void>() { @Override protected Task<Void> createTask() { return new Task<Void>() { @Override protected Void call() throws Exception { updateMessage("Lecture"); FileInputStream fSvg = new FileInputStream(Nom); ObjectInputStream svgBat = new ObjectInputStream(fSvg); scenario = (Jeu) svgBat.readObject(); final int nb = scenario.getNbVehicule() + 2; updateMessage("Graphismes"); updateProgress(1, nb); prepareScenario(); updateMessage("Carte"); fondDeCarte = new Visu(scenario.getLeTerrain(), scenario.getFeux()); vueCarte.setImage(fondDeCarte.getImage()); updateMessage("Calcul des mouvements"); int fig = 0; Pion unite = scenario.getPion(fig++); while (unite != null) { updateProgress(fig + 2, nb); if (!unite.estAlaBase()) affiche(unite); if (unite instanceof Vehicule) { ((Vehicule) unite).majCarc(paramJeu); if (!unite.estAlaBase()) doCalcNouvGraphePour((Vehicule) unite, unite.getX(), unite.getY()); } unite = scenario.getPion(fig++); } return null; } }; } }; loadGameService.setOnSucceeded((WorkerStateEvent event) -> { primaryScene.setCursor(oldCursor); centreEnXY(0, 0); changeZoom(1); corrigeLocation(0, 0); jgeCtrl.MAJ(); jgeCtrl.razMessage(); jgeCtrl.setUniteEnMain("", "", null); cmdCtrl.setMiniCarte(fondDeCarte.getImage()); cmdCtrl.enableAll(true); cmdCtrl.MAJCmd(); dialStage.close(); }); FXMLLoader loader = new FXMLLoader(); URL url = MainFF.class.getResource("view/DialAttente.fxml"); loader.setLocation(url); try { AnchorPane dial = (AnchorPane) loader.load(); dialStage.initModality(Modality.NONE); dialStage.initOwner(primaryStage); Scene scene = new Scene(dial); dialStage.setScene(scene); DialAttenteController dialCtrl = loader.getController(); dialCtrl.setDialStage(dialStage, "Charger"); dialCtrl.getProg().progressProperty().bind(loadGameService.progressProperty()); dialCtrl.getTache().textProperty().bind(loadGameService.messageProperty()); dialStage.show(); loadGameService.start(); } catch (IOException e) { e.printStackTrace(); } }
Je ne comprend pas bien ce qui se passe.le 13/05/2016 à 13:30 -
bouyeRédacteur/ModérateurEt, euh, ça plante où, comment, avec quoi comme message ?
Tu as essayer de tester avec une simple ProgressBar ou un ProgressMonitor dans un Stage au lieu de ton dialogue maison FXML ?le 13/05/2016 à 16:00