在JavaFX 8中管理多线程的最佳方法是什么? [英] What is the best way to manage multithreading in JavaFX 8?

查看:141
本文介绍了在JavaFX 8中管理多线程的最佳方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用多线程来找到影响JavaFX GUI元素的形状和内容的有效方法,例如简单的 Pane 。假设我有一个简单的 Pane ,我在给定的时间内显示填充的 Circle ,而我想要有可能回答他们,例如通过点击相应的键。到目前为止,为此,我尝试使用类实现 Runnable 接口并创建经典 Thread 对象在其中,为了从外部JavaFX Pane 中添加和/或删除元素,该元素在其构造函数参数中从主<$ c传递给该线程类 $ c>应用程序类。比如,两个类,1)应用程序和2)线程类,如下所示:

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 Circles 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:

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中是不可能的,而且还有以下异常的原因:

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:

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

...where the line "21" is: pane.getChildren().clear();

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

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?

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

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

After reading given answers I tried to implement given solutions in code, in order to display 10 custom Circles on different locations with specified time intervals between each display:

/* 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.

推荐答案

除了使用低级线程 API和 Platform.runLater(...)来安排在FX应用程序线程上执行的代码,如在Tomas的回答中,另一种选择是使用FX并发API。这提供了服务任务类,这些类旨在在后台线程上执行,以及保证的回调方法在FX应用程序线程上执行。

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.

对于你的简单例子,这看起来有点太多样板代码,但是对于真正的应用程序代码来说它非常好,提供了后台任务和后台任务之间的清晰分离。完成时执行的UI更新。此外,任务可以提交给执行者 s。

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, Tasks can be submitted to 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);
    }
}

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

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:

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

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

If you need to execute the pause multiple times, you can use a Timeline, and call 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 8中管理多线程的最佳方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆