Quelle est la meilleure façon de gérer le multithreading dans JavaFX 8?
J'essaie de trouver un moyen efficace d'influencer la forme et le contenu de la JavaFX les éléments d'interface graphique, comme simple Pane
, avec l'utilisation du multithreading. Disons que j'ai un simple Pane
, sur lequel j'ai d'affichage rempli Circle
s à la itervals de temps, et je veux avoir la possibilité de réponse, par exemple en appuyant sur la touche correspondante. Jusqu'à présent, à cette fin, j'ai essayé d'utiliser la classe avec la mise en œuvre de Runnable
de l'interface et de la création de classique Thread
objet, afin d'ajouter et/ou supprimer des éléments de l'extérieur, JavaFX Pane
, qui a été adoptée pour que "la classe thread" dans son paramètre du constructeur de la principale Application
classe. Dire, en deux classes, 1) et 2) de la classe thread, ressemble à ceci:
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class ClassApplication extends Application {
private Pane pane;
public Parent createContent() {
/* layout */
BorderPane layout = new BorderPane();
/* layout -> center */
pane = new Pane();
pane.setMinWidth(250);
pane.setMaxWidth(250);
pane.setMinHeight(250);
pane.setMaxHeight(250);
pane.setStyle("-fx-background-color: #000000;");
/* layout -> center -> pane */
Circle circle = new Circle(125, 125, 10, Color.WHITE);
/* add items to the layout */
pane.getChildren().add(circle);
layout.setCenter(pane);
return layout;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setWidth(300);
stage.setHeight(300);
stage.show();
/* initialize custom Thread */
ClassThread thread = new ClassThread(pane);
thread.execute();
}
public static void main(String args[]) {
launch(args);
}
}
...et la "classe thread"
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class ClassThread implements Runnable {
private Thread t;
private Pane pane;
ClassThread(Pane p) {
this.pane = p;
t = new Thread(this, "Painter");
}
@Override
public void run() {
try {
Thread.sleep(2000);
Circle circle = new Circle(50, 50, 10, Color.RED);
pane.getChildren().clear();
pane.getChildren().add(circle);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
public void execute() {
t.start();
}
}
Toutefois, une telle solution, où, en application Swing pourrait être possible, en JavaFX est impossible, et en plus, c'est la raison de l'exception suivante:
Exception in thread "Painter" java.lang.IllegalStateException: Not on FX application thread; currentThread = Painter
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Unknown Source)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(Unknown Source)
at javafx.scene.Parent$2.onProposedChange(Unknown Source)
at com.sun.javafx.collections.VetoableListDecorator.clear(Unknown Source)
at ClassThread.run(ClassThread.java:21)
at java.lang.Thread.run(Unknown Source)
...où la ligne "21" est: pane.getChildren().clear();
J'en ai conclu que "il y a un problème avec influençant le principal JavaFX fil au niveau d'un autre thread". Mais dans ce cas, comment puis-je changer la JavaFX des éléments d'interface de la forme et du contenu de façon dynamique, si je ne peux pas (tbh avec plus de précision dire sera "je ne sais pas comment") lier ensemble, quelques fils?
Mise à JOUR : 2014/08/07, 03:42
Après avoir lu donné les réponses, j'ai essayé de mettre en œuvre étant donné les solutions dans le code, afin d'afficher 10 personnalisée Circle
s sur des endroits différents avec des intervalles de temps entre chaque affichage:
/* in ClassApplication body */
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setWidth(300);
stage.setHeight(300);
stage.show();
Timeline timeline = new Timeline();
for (int i = 0; i < 10; i++) {
Random r = new Random();
int random = r.nextInt(200) + 25;
KeyFrame f = new KeyFrame(Duration.millis((i + 1) * 1000),
new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent ae) {
pane.getChildren().add(new Circle(
random, random, 10, Color.RED));
}
});
timeline.getKeyFrames().add(f);
}
timeline.setCycleCount(1);
timeline.play();
}
La solution ci-dessus fonctionne très bien. Je vous remercie beaucoup.
OriginalL'auteur bluevoxel | 2014-08-06
Vous devez vous connecter pour publier un commentaire.
En plus de l'utilisation du faible niveau
Thread
API etPlatform.runLater(...)
pour planifier l'exécution de code sur le FX Thread d'Application, comme dans Tomas réponse, une autre option est d'utiliser les FX de la simultanéité de l'API. Cette offreService
etTask
classes, qui sont destinés à être exécutés sur les threads d'arrière-plan, ainsi que des méthodes de rappel qui sont garantis pour être exécuté sur le FX Thread d'Application.Pour votre exemple simple, cela ressemble un peu trop de code réutilisable, mais pour de vrai code de l'application, il est très agréable, offrant une séparation nette entre les tâches d'arrière-plan et de l'INTERFACE utilisateur mise à jour est effectuée à la fin. En outre,
Task
s peut être soumis àExecutor
s.Si tout ce que vous faites est une pause, vous pouvez même sortir avec l'aide (ou à l'abus?) l'animation de l'API. Il y a un
PauseTransition
qui met en pause pendant un certain temps, et vous pouvez utiliser sesonFinished
gestionnaire pour exécuter la mise à jour:Si vous avez besoin d'exécuter la pause plusieurs fois, vous pouvez utiliser un
Timeline
, et d'appelersetCycleCount(...)
:FxTimer.runLater(Duration.ofMillis(2000), () -> pane.getChildren().setAll(new Circle(50, 50, 10, Color.RED)));
Le problème avec cette solution est que, c'est bon pour un simple retard événements. Mais dans le cas de l'affichage de plusieurs
Circle
s dans certains intervalles de temps, c'est problématique. Dites, j'ai 10Circle
s et je veux les afficher une par une, par exemple 2000ms d'intervalle sur le mêmePane
. J'ai aussi essayé d'utiliserTimeline
et c'estKeyValue
etKeyFrame
, mais le problème c'est que dansKeyValue
objet que je ne pouvez pas ajouter ou supprimer des éléments de laPane
.Ajout d'un exemple de ce qui se répète 10 fois. Vous pouvez définir le nombre de cycles à
Animation.INDEFINITE
de répéter indéfiniment. Et @TomasMikula sans aucun doute un one-liner de ReactFX pour la même chose ;).Répéter indéfiniment, il suffit de changer
runLater
dans mon commentaire précédent àrunPeriodically
. Je n'ai pas de solution facile, pour un nombre fixe de répétitions. J'imagine quelque chose comme ceci:EventStreams.ticks(Duration.ofMillis(2000)).limit(10).subscribe(x -> pane.getChildren().setAll(new Circle(50, 50, 10, Color.RED)))
, à l'exception de lalimit
opérateur n'est pas là. C'est juste, personne n'a demandé encore 🙂Dans votre code, vous ajoutez 10
KeyFrame
s, toutes exécuter à l'heure=1000ms. Si vous souhaitez utiliser la clé 10 images à la place de 10 cycles, puis utilisezDuration.millis((i+1) * 1000)
. Une image clé est la durée est paradoxalement pas la durée de l'image clé, mais la durée depuis le début de l'animation.OriginalL'auteur James_D
Vous pouvez accéder à la scène qu'à partir de la JavaFX application thread. Vous avez besoin d'envelopper votre code qui accède à la scène graphique dans
Platform.runLater()
:OriginalL'auteur Tomas Mikula