IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Bonnes pratiques pour exécuter une tâche de fond en JavaFX,
Un tutoriel de Fabrice Bouyé

Le , par bouye

0PARTAGES

5  0 
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.

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de Phoste
Nouveau membre du Club https://www.developpez.com
Le 01/03/2015 à 23:21
Bonjour, 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 : Sélectionner tout
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
import 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.
    }
    
}
Alors que la partie de code qui permet de publier un message à la base de donnée mqtt ne pose aucun soucis et ne renvoie par d'exception :

Code : Sélectionner tout
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
import 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();
        }
    }
    
}
L'idée est que le LED réagisse au changement de valeur de la base de donnée mqtt

Merci d'avance
0  0 
Avatar de bouye
Rédacteur/Modérateur https://www.developpez.com
Le 02/03/2015 à 4:14
Pour 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 : Sélectionner tout
1
2
3
4
MqttClient client = new MqttClient(BROKER, Led.this.id);
client.setCallback(Led.this);
client.connect();
client.subscribe("A000009/"+Led.this.name);
Il faudrait donc trouver ce dont il s'agit.
Ça serait plus facile si je savais quelle ligne provoque l'erreur
0  0 
Avatar de Phoste
Nouveau membre du Club https://www.developpez.com
Le 02/03/2015 à 18:11
Alors, niveau des erreurs j'obtiens ceci :

Code : Sélectionner tout
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
Exception 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)
J'espère que cela pourra t'aider car moi je bloque ...
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)
0  0 
Avatar de bouye
Rédacteur/Modérateur https://www.developpez.com
Le 02/03/2015 à 23:01
Le 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 : Sélectionner tout
at simulateur_v2.Led.messageArrived(Led.java:60)
Il faut donc s'assurer que le message arrive et est traité dans le JavaFX Application Thread puisque ce dernier manipule la scène (via la modif de style).

Code : Sélectionner tout
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);
        }
    }
0  0 
Avatar de Phoste
Nouveau membre du Club https://www.developpez.com
Le 03/03/2015 à 11:56
En 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 solution merci !
Je n'ai même pas besoin de me connecter à la db sur un autre thread (il le fait peut-être seul ?)
0  0 
Avatar de bouye
Rédacteur/Modérateur https://www.developpez.com
Le 03/03/2015 à 20:54
Faut 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.
0  0 
Avatar de foxfx
Nouveau Candidat au Club https://www.developpez.com
Le 23/03/2015 à 17:50
Citation Envoyé par bouye Voir le message
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.
J'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é.
0  0 
Avatar de bouye
Rédacteur/Modérateur https://www.developpez.com
Le 23/03/2015 à 22:13
Merci, ça aura échappé aux multiples relectures (/doh). Je corrige de suite.
0  0 
Avatar de Gouyon
Membre expérimenté https://www.developpez.com
Le 13/05/2016 à 13:30
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 : Sélectionner tout
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
public 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();
		}
		
	}
Du coups j'ai voulu mettre une boite de dialogue qui montre la progression du chargement. J'ai donc écrit le code ci dessous

Code : Sélectionner tout
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
83
	private 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();
		}

	}
Mais j'ai un allongement extraordinaire du temps d'exécution supérieur à la minute avec au final des exceptions de pointeur null car il semble terminer la tache avant la fin.
Je ne comprend pas bien ce qui se passe.
0  0 
Avatar de bouye
Rédacteur/Modérateur https://www.developpez.com
Le 13/05/2016 à 16:00
Et, 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 ?
0  0