在JavaFX 8中管理multithreading的最佳方法是什么?

我正在尝试使用multithreading来找到影响JavaFX GUI元素的形状和内容的有效方法,例如简单的Pane 。 假设我有一个简单的Pane ,我在给定的时间点上显示填充的Circle ,我想要有可能回答它们,例如通过敲击相应的键。 到目前为止,为此目的,我尝试使用类与Runnable接口的实现并在其中创建经典的Thread对象,以便从外部JavaFX Pane添加和/或删除元素,这些元素被传递给那个“线程类” “在其主要Application类的构造函数参数中。 比如,两个类,1)应用程序和2)线程类,看起来像这样:

 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); } } 

……和“线程类”

 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(); } } 

但是,这样的解决方案,在Swing应用程序中是可能的,在JavaFX中是不可能的,而且还有以下exception的原因:

 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) 

…“21”行是: pane.getChildren().clear();

我得出结论,“从另一个线程的级别影响主JavaFX线程存在问题”。 但在这种情况下,如果我不能(更准确地说将“我不知道怎么样”)绑定到几个线程,我怎么能动态地改变JavaFX GUI元素的形状和内容?

更新时间:2014/08/07,03:42

在阅读给定答案后,我尝试在代码中实现给定的解决方案,以便在每个显示器之间以指定的时间间隔在不同位置显示10个自定义Circle

 /* 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() { @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(); } 

上面的解决方案很好用。 非常感谢你。

除了使用低级Thread API和Platform.runLater(...)来调度要在FX应用程序线程上执行的代码之外,就像Tomas的回答一样,另一种选择是使用FX并发API。 这提供了ServiceTask类,它们旨在在后台线程上执行,以及保证在FX应用程序线程上执行的回调方法。

对于您的简单示例,这看起来有点太多样板代码,但对于真正的应用程序代码,它非常好,在后台任务和完成时执行的UI更新之间提供了一个干净的分离。 另外, Task可以提交给Executor

 import javafx.application.Application; import javafx.concurrent.Task ; 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(); Task task = new Task() { @Override public Void call() throws Exception { Thread.sleep(2000); return null ; } }; task.setOnSucceeded(event -> { Circle circle = new Circle(50, 50, 10, Color.RED); pane.getChildren().setAll(circle); }); new Thread(task).run(); } public static void main(String args[]) { launch(args); } } 

如果你所做的只是暂停,你甚至可以使用(或滥用?)动画API。 有一个暂停指定时间的PauseTransition ,你可以使用它的onFinished处理程序来执行更新:

 import javafx.application.Application; import javafx.animation.PauseTransition ; 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; import javafx.util.Duration ; 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(); PauseTransition pause = new PauseTransition(Duration.millis(2000)); pause.setOnFinished(event -> pane.getChildren().setAll(new Circle(50, 50, 10, Color.RED))); pause.play(); } public static void main(String args[]) { launch(args); } } 

如果需要多次执行暂停,可以使用Timeline ,并调用setCycleCount(...)

 import javafx.application.Application; import javafx.animation.Timeline ; 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; import javafx.util.Duration ; 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(); KeyFrame keyFrame = new KeyFrame(Duration.millis(2000), event -> pane.getChildren().setAll(new Circle(50, 50, 10, Color.RED))); Timeline timeline = new Timeline(keyFrame); // Repeat 10 times: timeline.setCycleCount(10); timeline.play(); } public static void main(String args[]) { launch(args); } } 

您只能从JavaFX应用程序线程访问场景。 您需要包装访问Platform.runLater()的场景图的代码:

 Platform.runLater(() -> { Circle circle = new Circle(50, 50, 10, Color.RED); pane.getChildren().clear(); pane.getChildren().add(circle); });