Multithreading JavaFX管理绑定到场景生命周期的轮询线程

Multithreading JavaFX管理绑定到场景生命周期的轮询线程,multithreading,javafx,Multithreading,Javafx,在下面的示例中,有一个FXML控制器运行一个单独的线程来定期更新组件 如果用户单击导航离开此控制器并返回此控制器,则将有2个线程在后台运行。(第一个从未停止过) 如何确保只有一个线程,并且该线程与控制器的生命周期相关联 <BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="example.BorderController" fx:id="rootPane">

在下面的示例中,有一个FXML控制器运行一个单独的线程来定期更新组件

如果用户单击导航离开此控制器并返回此控制器,则将有2个线程在后台运行。(第一个从未停止过)

如何确保只有一个线程,并且该线程与控制器的生命周期相关联

<BorderPane xmlns:fx="http://javafx.com/fxml/1" 
            fx:controller="example.BorderController"
            fx:id="rootPane">
    <left>
        <VBox>
        <Button text="Pane1" onAction"#goToPane1"/>
        <Button text=Pane2" onAction="goToPane2"/>
        </VBox>
    </left>
</BorderPane>

在两个窗格之间导航后,输出如下所示:

Pane1 example.Pane1@45892b4c
Pane1 example.Pane1@6dc7392e
Pane1 example.Pane1@45892b4c
Pane1 example.Pane1@6dc7392e
// Repeats

您可以在窗格中观察节点的
sceneProperty()
,并相应地启动或关闭
ScheduledExecutor

import java.net.URL;
import java.time.Instant;
import java.util.ResourceBundle;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;

public class Pane1Controller implements Initializable {

    @FXML
    private Label toUpdate;

    @FXML
    private Pane root;

    private ScheduledExecutorService scheduler;

    @Override
    public void initialize(URL url, ResourceBundle rb) {

        root.sceneProperty().addListener((obs, oldScene, newScene) -> {
            if (newScene == null && scheduler != null) {
                scheduler.shutdown();
            }
            if (newScene != null) {

                scheduler = Executors.newScheduledThreadPool(1, runnable -> {
                    Thread t = new Thread(runnable);
                    t.setDaemon(true);
                    return t ;
                });

                scheduler.scheduleAtFixedRate(() -> {
                    Platform.runLater(() -> {
                        System.out.println("Pane1 " + this);
                        toUpdate.setText(Instant.now().toString());
                    });
                } , 2, 2, TimeUnit.SECONDS);
            }
        });

    }
}
窗格中的任何节点通常都可以工作,不过使用窗格本身(我在这里给出了
fx:id=“root”
)可能最有意义。注意,我还将执行器使用的线程设置为守护进程线程,因此它不会阻止应用程序退出

最后,请注意,对于示例中使用的功能(其中计划任务中的所有内容都在FX应用程序线程上执行),动画API可能比
java.util.concurrent
API更干净,尽管实际应用程序可能需要后者:

import java.net.URL;
import java.time.Instant;
import java.util.ResourceBundle;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.util.Duration;

public class Pane1Controller implements Initializable {

    @FXML
    private Label toUpdate;

    @FXML
    private Pane root;

    private Timeline timeline ;

    @Override
    public void initialize(URL url, ResourceBundle rb) {

        timeline = new Timeline(new KeyFrame(Duration.seconds(2), e-> {
            System.out.println("Pane1 " + this);
            toUpdate.setText(Instant.now().toString());         
        }));
        timeline.setCycleCount(Animation.INDEFINITE);

        root.sceneProperty().addListener((obs, oldScene, newScene) -> {
            if (newScene == null) {
                timeline.pause();
            } else {
                timeline.play();
            }
        });

    }
}
import java.net.URL;
import java.time.Instant;
import java.util.ResourceBundle;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;

public class Pane1Controller implements Initializable {

    @FXML
    private Label toUpdate;

    @FXML
    private Pane root;

    private ScheduledExecutorService scheduler;

    @Override
    public void initialize(URL url, ResourceBundle rb) {

        root.sceneProperty().addListener((obs, oldScene, newScene) -> {
            if (newScene == null && scheduler != null) {
                scheduler.shutdown();
            }
            if (newScene != null) {

                scheduler = Executors.newScheduledThreadPool(1, runnable -> {
                    Thread t = new Thread(runnable);
                    t.setDaemon(true);
                    return t ;
                });

                scheduler.scheduleAtFixedRate(() -> {
                    Platform.runLater(() -> {
                        System.out.println("Pane1 " + this);
                        toUpdate.setText(Instant.now().toString());
                    });
                } , 2, 2, TimeUnit.SECONDS);
            }
        });

    }
}
import java.net.URL;
import java.time.Instant;
import java.util.ResourceBundle;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.util.Duration;

public class Pane1Controller implements Initializable {

    @FXML
    private Label toUpdate;

    @FXML
    private Pane root;

    private Timeline timeline ;

    @Override
    public void initialize(URL url, ResourceBundle rb) {

        timeline = new Timeline(new KeyFrame(Duration.seconds(2), e-> {
            System.out.println("Pane1 " + this);
            toUpdate.setText(Instant.now().toString());         
        }));
        timeline.setCycleCount(Animation.INDEFINITE);

        root.sceneProperty().addListener((obs, oldScene, newScene) -> {
            if (newScene == null) {
                timeline.pause();
            } else {
                timeline.play();
            }
        });

    }
}