JavaFX8 - 从不同的线程引用FXML对象

时间:2014-09-30 02:29:23

标签: java concurrency javafx javafx-8 fxml

所以我有一个JavaFX8应用程序,我正在尝试创建在每次迭代时更改GUI元素的计划线程。我正在使用ScheduledExecutorService,但是如果run()方法中的任何代码引用了FXML对象,它就会死掉而没有任何错误。

此代码位于我的主FXML窗口的Controller中。 fxmlLabel声明为@FXML private Label fxmlLabel;

此代码有效,并正确打印" Hello World!"每一秒。

Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello world!");
    }
};
executor.scheduleAtFixedRate(task, 1, 1, TimeUnit.SECONDS);

此代码打印" Hello World!"曾经,没有任何反应。

Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello world!");
        fxmlLabel.setText("Bye world!");
        System.out.println("Hello again!");
    }
};
executor.scheduleAtFixedRate(task, 1, 1, TimeUnit.SECONDS);

1 个答案:

答案 0 :(得分:4)

您无法从后台线程更改作为显示的场景图形一部分的UI元素的状态。在Java 8中,这会引发异常。在早期版本的JavaFX中,它可能会抛出异常或者可能会无声地失败。

要解决此问题,请在Platform.runLater(...)中包装更新场景图的调用:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class UpdateLabelRegularly extends Application {

    @Override
    public void start(Stage primaryStage) {
        Label label = new Label();
        VBox root = new VBox();
        root.getChildren().add(label);
        ScheduledExecutorService exec = Executors.newScheduledThreadPool(5, r -> {
           Thread t = new Thread(r);
           t.setDaemon(true);
           return t ;
        });
        AtomicInteger count = new AtomicInteger();
        Runnable task = () -> {
            System.out.println("Hello world!");
            Platform.runLater(() -> label.setText("Count: "+count.incrementAndGet()));
            System.out.println("Hello again");
        };
        exec.scheduleAtFixedRate(task, 1, 1, TimeUnit.SECONDS);
        Scene scene = new Scene(root, 250, 75);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

或使用javafx.concurrent.ScheduledService类:

import java.util.concurrent.atomic.AtomicInteger;

import javafx.application.Application;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class UpdateLabelRegularly extends Application {

    @Override
    public void start(Stage primaryStage) {
        Label label = new Label();
        VBox root = new VBox();
        root.getChildren().add(label);
        AtomicInteger count = new AtomicInteger();
        ScheduledService<String> service = new ScheduledService<String>() {
            @Override
            protected Task<String> createTask() {
                Task<String> task = new Task<String>() {
                    @Override
                    public String call() {
                        return "Count: "+count.incrementAndGet();
                    }
                };
                return task ;
            }
        };
        service.setOnSucceeded(event -> label.setText(service.getValue()));
        service.setDelay(Duration.seconds(1));
        service.setPeriod(Duration.seconds(1));
        service.start();
        Scene scene = new Scene(root, 250, 75);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

一般来说,第二种方法更好(imho),因为它在任务逻辑和完成时UI的更新之间提供了很好的分离。有关更多示例,请参阅Javadocs for TaskScheduledService

还有一次更新: 为了完整起见,这里有一个使用ScheduledExecutorServicejavafx.concurrent API的版本,以防万一您有被迫这样做的原因:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class UpdateLabelRegularly extends Application {

    @Override
    public void start(Stage primaryStage) {
        Label label = new Label();
        VBox root = new VBox();
        root.getChildren().add(label);
        AtomicInteger count = new AtomicInteger();
        ScheduledExecutorService exec = Executors.newScheduledThreadPool(5, r -> {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t ;
        });
        exec.scheduleAtFixedRate(() -> {
            Task<String> task = new Task<String>() {
                @Override
                public String call() {
                    return "Count: "+count.incrementAndGet();
                }
            };
            task.setOnSucceeded(event -> label.setText(task.getValue()));
            task.run();
        }, 1, 1, TimeUnit.SECONDS); 

        Scene scene = new Scene(root, 250, 75);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}