java 在 JavaFX 8 中管理多线程的最佳方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25171039/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the best way to manage multithreading in JavaFX 8?
提问by bluevoxel
I'm trying to find an efficient way to influence the shape and the content of the JavaFX GUI elements, such as simple Pane
, with use of multithreading. Let's say I have a simple Pane
, on which I display filled Circle
s at the given itervals of time, and I want to have the posibility to answer to them, e.g. by hitting the corresponding key. So far, for this purpose, I tried to use class with the implementation of Runnable
interface and creation of classic Thread
object in it, in order to add and/or remove elements from the external JavaFX Pane
, which was passed to that "thread class" in its constructor parameter from the main Application
class. Say, both classes, 1) application and 2) thread class, looks like this:
我试图找到一种有效的方法来影响 JavaFX GUI 元素的形状和内容,例如 simple Pane
,并使用多线程。假设我有一个简单的Pane
,我Circle
在给定的时间间隔上显示填充的s,并且我希望有可能回答它们,例如通过点击相应的键。到目前为止,为此目的,我尝试使用具有Runnable
接口实现和经典Thread
对象创建的类,以便从外部 JavaFX 添加和/或删除元素,该外部 JavaFXPane
被传递给它的“线程类”来自主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);
}
}
...and the "thread class"
...和“线程类”
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();
}
}
However, such a solution, where in Swing application could be possible, in JavaFX is impossible, and what's more, is the reason of the following exception:
然而,这样的解决方案,在 Swing 应用程序中是可能的,在 JavaFX 中是不可能的,而且,是以下异常的原因:
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.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)
...where the line "21" is: pane.getChildren().clear();
...其中“21”行是: pane.getChildren().clear();
I've concluded, that "there is a problem with influencing the main JavaFX thread from the level of another thread". But in this case, how can I change the JavaFX GUI elements shape and content dynamically, if I can't (tbh more accurately to say will be "I don't know how") bind togheter few threads?
我得出的结论是,“从另一个线程的级别影响主 JavaFX 线程存在问题”。但是在这种情况下,如果我不能(更准确地说是“我不知道如何”)将几个线程绑定在一起,我该如何动态更改 JavaFX GUI 元素的形状和内容?
UPDATE : 2014/08/07, 03:42
更新 : 2014/08/07, 03:42
After reading given answers I tried to implement given solutions in code, in order to display 10 custom Circle
s on different locations with specified time intervals between each display:
阅读给定的答案后,我尝试在代码中实现给定的解决方案,以便Circle
在不同位置显示 10 个自定义s,每个显示之间具有指定的时间间隔:
/* 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();
}
The solution above works just fine. Thank you very much.
上面的解决方案工作得很好。非常感谢你。
回答by James_D
In addition to using the low-level Thread
API and Platform.runLater(...)
to schedule code to be executed on the FX Application Thread, as in Tomas' answer, another option is to use the FX concurrency API. This provides Service
and Task
classes, which are intended to be executed on background threads, along with callback methods which are guaranteed to be executed on the FX Application Thread.
除了使用低级Thread
API 并Platform.runLater(...)
安排要在 FX 应用程序线程上执行的代码(如 Tomas 的回答)之外,另一种选择是使用 FX 并发 API。这提供了Service
与Task
类,它们旨在被对后台线程执行时,与这些都保证将要在FX应用程序线程执行回调方法沿。
For your simple example, this looks like a bit too much boilerplate code, but for real application code it is quite nice, providing a clean separation between the background task and the UI update that is performed on completion. Additionally, Task
s can be submitted to Executor
s.
对于您的简单示例,这看起来有点太多样板代码,但对于真正的应用程序代码,它非常好,在后台任务和完成时执行的 UI 更新之间提供了清晰的分离。此外,Task
s 可以提交给Executor
s。
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<Void> task = new Task<Void>() {
@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);
}
}
If all you are doing is pausing, you can even get away with using (or abusing?) the animation API. There's a PauseTransition
that pauses for a specified time, and you can use its onFinished
handler to execute the update:
如果您所做的只是暂停,您甚至可以使用(或滥用?)动画 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);
}
}
If you need to execute the pause multiple times, you can use a Timeline
, and call setCycleCount(...)
:
如果您需要多次执行暂停,可以使用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);
}
}
回答by Tomas Mikula
You can access the scene only from the JavaFX application thread. You need to wrap your code that accesses the scene graph in Platform.runLater()
:
您只能从 JavaFX 应用程序线程访问场景。您需要将访问场景图的代码包装在Platform.runLater()
:
Platform.runLater(() -> {
Circle circle = new Circle(50, 50, 10, Color.RED);
pane.getChildren().clear();
pane.getChildren().add(circle);
});