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

时间:2014-08-06 22:05:09

标签: java multithreading javafx javafx-8

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

Application

...和“线程类”

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

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

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

...“21”行是: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)

我得出结论,“从另一个线程的级别影响主JavaFX线程存在问题”。但是在这种情况下,如果我不能(更准确地说将“我不知道怎

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

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

Circle

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

2 个答案:

答案 0 :(得分:7)

除了使用低级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<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);
    }
}

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

答案 1 :(得分:2)

您只能从JavaFX应用程序线程访问场景。您需要在Platform.runLater()

中包装访问场景图的代码
Platform.runLater(() -> {
    Circle circle = new Circle(50, 50, 10, Color.RED);
    pane.getChildren().clear();
    pane.getChildren().add(circle);
});