在JavaFX 8中管理多线程的最佳方法是什么?

blu*_*xel 5 java multithreading javafx javafx-8

我正在尝试Pane使用多线程来找到影响JavaFX GUI元素的形状和内容的有效方法,例如简单.假设我有一个简单的Pane,我Circle在给定的时间点显示填充s,我想有可能回答它们,例如通过敲击相应的键.到目前为止,为此目的,我尝试使用类来实现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);
    }
}
Run Code Online (Sandbox Code Playgroud)

......和"线程类"

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,这样的解决方案,在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$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)
Run Code Online (Sandbox Code Playgroud)

......"21"行是: pane.getChildren().clear();

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

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

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

/* 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();
}
Run Code Online (Sandbox Code Playgroud)

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

Tom*_*ula 7

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

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


Jam*_*s_D 7

除了使用低级ThreadAPI并Platform.runLater(...)安排在FX应用程序线程上执行的代码之外,如在Tomas的回答中,另一个选择是使用FX并发API.这提供了用于在后台线程上执行的类ServiceTask类,以及保证在FX应用程序线程上执行的回调方法.

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您所做的只是暂停,您甚至可以使用(或滥用?)动画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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果需要多次执行暂停,可以使用a 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);
    }
}
Run Code Online (Sandbox Code Playgroud)