管理与场景生命周期相关的轮询线程的JavaFX

时间:2016-10-25 08:38:43

标签: multithreading javafx

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

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

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

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

public class BorderController implements Initializable {

    @FXML private BorderPane rootPane;

    @FXML
    public void goToPane1(ActionEvent event) {
        showPage("fxml/Pane1");
    }

    @FXML
    public void goToPane2(ActionEvent event) {
        showPage("fxml/Pane2");
    }

    private void showPage(final Stringpage) {
        try {
            rootPane.setCenter((Node) FXMLLoader.load(getClass().getResource(page)));

        } catch (final IOException ex) {
            LOGGER.log(Level.INFO, "An error occurred loading the page.", ex);
        }
    }
}

public class Pane1Controller implements Initializable {

    @FXML private Label toUpdate;


    @Override
    public void initialize(URL url, ResourceBundle rb) {
                final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(() -> {
            Platform.runLater(() -> {
                System.out.println("Pane1 " + this);
                toUpdate.setText(Instant.now().toString());
            });
        }, 2, 2, SECONDS);
    }
}

在2个窗格之间导航后,输出如下:

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

1 个答案:

答案 0 :(得分:1)

您可以在窗格中观察节点的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();
            }
        });

    }
}