更改根节点时后台线程未关闭[javaFX]

更改根节点时后台线程未关闭[javaFX],java,multithreading,javafx,nfc,Java,Multithreading,Javafx,Nfc,我正在创建一个javafx程序,该程序的导航菜单通过更改场景的根来工作。根都继承自Pane类。一些窗格具有运行的后台线程。但是,当事件处理程序更改根窗格时,该窗格会切换,但后台线程不会停止。这会导致线程读取NFC时出现问题,并导致多个线程尝试读取NFC读取器。 如何关闭后台线程?(从创建线程的窗格外部)或者我是否需要以不同的方式设置线程。 (线程设置为守护进程)。 线程在窗格构造函数中创建,如下所示: (我假设,当切换窗格时,它们属于该窗格,线程将停止。事实并非如此) 我以静态方式切换窗格,如下

我正在创建一个javafx程序,该程序的导航菜单通过更改场景的根来工作。根都继承自Pane类。一些窗格具有运行的后台线程。但是,当事件处理程序更改根窗格时,该窗格会切换,但后台线程不会停止。这会导致线程读取NFC时出现问题,并导致多个线程尝试读取NFC读取器。
如何关闭后台线程?(从创建线程的窗格外部)或者我是否需要以不同的方式设置线程。 (线程设置为守护进程)。 线程在窗格构造函数中创建,如下所示: (我假设,当切换窗格时,它们属于该窗格,线程将停止。事实并非如此)

我以静态方式切换窗格,如下所示:(此方法位于自己的类中)


您应该添加一种方法,将侦听器注册到负责替换场景根的类中。这允许您收到此类更改的通知,并通过终止线程作出反应

示例

@FunctionalInterface
public interface NodeReplaceListener {

    public void onNodeReplace();

}

顺便说一句:请注意,对场景的修改不应来自任何线程,而应来自应用程序线程。使用
Platform.runLater
从不同线程进行更新:

Platform.runLater(() -> signIn.setText("Scan your card to sign in/out"));
正如你所说:

一些窗格具有运行的后台线程

我假设您在自己的
窗格
实现中创建这些线程。我将向您展示一个包含抽象类的解决方案,您的每个窗格(或者至少是作为根插入的窗格)都应该扩展该抽象类。如果你没有,我强烈建议你这样做。但是,您没有提供关于这些窗格的太多信息,因此我将把我的答案集成到代码中的任务留给您(如果您决定遵循它的话)

稍后我将返回到
volatile
关键字

现在您可以在
activateThread
方法中创建线程,最好是在
OwnPane
或其子类中,例如:

public void activateNFCThread(){
    Runnable r = new Runnable(){
            @Override
            public void run () {
                 while(isRoot){
                      // what the thread has to do ...
                 }
            }
    };
    Thread nfcCheckThread = new Thread(r);
    nfcCheckThread.setDaemon(true);
    nfcCheckThread.start();
}
现在我可以解释为什么必须使用
volatile
关键字:字段
isRoot
将由不同的线程使用。使用
volatile
关键字,我们确保所有线程都将访问相同的“变量”(否则,出于性能原因,每个线程将有自己的版本)。
线程是在
OwnPane
(或子类)中的方法中创建的,允许从
Runnable
对象中访问
isRoot
字段。在
OwnPane
的子类中,甚至可以重写
setAsRoot
方法,以便在调用
setAsRoot
方法时直接启动NFS线程(如果需要):

最后,您可以使用以下方法更改舞台中场景的根窗格:

// All your methods regarding stage changes are static, so I'll leave this one static too
public static void changeRoot(Stage stage, OwnPane newRoot){
    OwnPane oldStage = (OwnPane)stage.getScene().getRoot();
    oldStage.unsetAsRoot();
    Platform.runLater(() -> {   //Platform.runLater to be sure the main thread that will execute this 
                                //(only main thread is allowed to change something in the JavaFX nodes)
        stage.getScene().setRoot(newRoot);
        newRoot.setAsRoot();
    });
}

当jvm退出或main返回时,守护进程线程退出。如果要提前退出,则需要手动关闭它们。执行此操作的标准方法是让回退线程定期检查“keepGoing”标志,您可以在希望它们退出时取消设置该标志。作为deamon线程的线程除了不阻止运行时关闭之外没有任何效果。您需要实现一些逻辑来自己关闭这些线程。。。可能您可以使用一些从侦听器更新到
场景
根目录的
原子引用
,它允许
线程
s检查场景的根目录是否仍然是“它们的”根目录,并以其他方式终止,但是很难推荐任何内容,因为关于您的设计的信息量很小。。。顺便说一句,使用executor服务可能会有所帮助。如果您没有在代码中显示线程是如何创建和关闭的,我们无法确定您是否需要以不同的方式设置线程!请提供一些重新创建您的情况的方法。它应该检查什么以确定它是否应该继续运行我尝试使用:if(getScene().getRoot().isDisable());但是它不起作用。你在哪里定义你的第一个代码样本被调用??HomePane是自制的吗??
private Parent root;
private NodeReplaceListener listener;
private Scene scene;

public void setRoot(Parent root, NodeReplaceListener listener) {
    if (root != this.root && this.listener != null) {
        this.listener.onNodeReplace();
    }
    this.root = root;
    this.listener = listener;

    scene.setRoot(root);
}

@Override
public void start(Stage primaryStage) {
    Button btn = new Button("Next Scene");
    btn.setOnAction((ActionEvent event) -> {
        // replace root
        setRoot(new StackPane(new Rectangle(100, 100)), null);
    });

    StackPane root = new StackPane();
    root.getChildren().add(btn);

    scene = new Scene(new Group(), 100, 100);

    class MyRunnable implements Runnable {

        // running volatile to guarantee that visibility of written values to all threads
        volatile boolean running = true;

        int i;

        @Override
        public void run() {
            while (running) {
                System.out.println(i++);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                }
            }
        }

        public void cancel() {
            running = false;
        }

    }

    MyRunnable r = new MyRunnable();
    Thread t = new Thread(r);
    t.setDaemon(true);
    t.start();

    primaryStage.setScene(scene);

    // cancel runnable when root is replaced
    setRoot(root, r::cancel);
    primaryStage.show();
}
Platform.runLater(() -> signIn.setText("Scan your card to sign in/out"));
public abstract class OwnPane extends Pane {
    protected volatile boolean isRoot = false;

    public void setAsRoot(){
        isRoot = true;
    }

    public void unsetAsRoot(){
        isRoot = false;
    }
}
public void activateNFCThread(){
    Runnable r = new Runnable(){
            @Override
            public void run () {
                 while(isRoot){
                      // what the thread has to do ...
                 }
            }
    };
    Thread nfcCheckThread = new Thread(r);
    nfcCheckThread.setDaemon(true);
    nfcCheckThread.start();
}
public class PaneWithNFCReader extends OwnPane {
    @Override
    public void setAsRoot(){
         super.setAsRoot();
         activateNFCThread();
    }
}
// All your methods regarding stage changes are static, so I'll leave this one static too
public static void changeRoot(Stage stage, OwnPane newRoot){
    OwnPane oldStage = (OwnPane)stage.getScene().getRoot();
    oldStage.unsetAsRoot();
    Platform.runLater(() -> {   //Platform.runLater to be sure the main thread that will execute this 
                                //(only main thread is allowed to change something in the JavaFX nodes)
        stage.getScene().setRoot(newRoot);
        newRoot.setAsRoot();
    });
}