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

时间:2016-12-23 11:43:00

标签: java multithreading javafx nfc

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

  Runnable r = new Runnable() {
        @Override
         public void run () {
             boolean cont = true;
             while(cont){

                try {
                    NFCcard create1 = new NFCcard();

                    String staffID=create1.getCardID().toString();
                    staffID = staffID.replaceAll("\\D+","");
                    signIn.setText("Welcome "+getUserName(staffID)+getPhotoSrc(create1.getCardID().toString()));

                    Thread.sleep(1000);

                } catch (CardException e) {


                } catch (InterruptedException e) {

                }
                signIn.setText("Scan your card to sign in/out");
                if(getScene().getRoot().isDisable());
                    cont=false;
             }

            }};

     Thread nfcCheckThread = new Thread(r);
     nfcCheckThread.setDaemon(true);
     nfcCheckThread.start();

我以这样的静态方式切换Panes :(这个方法在自己的类中)。

   public static void homeButtonhandler(Stage stage){ 
     HomePane mainPane1=new HomePane(stage, new HomeContent(stage));
     stage.getScene().setRoot(mainPane1);
     }
 public static void adminButtonhandler(Stage stage){

      DialogBox dialog = new  DialogBox();

      try{
        Optional<String> result = dialog.showAndWait();
        if (result.get().equals("115")){
            AdminPane adminPane1 = new AdminPane(stage,new Content(stage));
            stage.getScene().setRoot(adminPane1);
            }}

        catch(NoSuchElementException Exception){

        }


 }

public static void workingTodayButtonhandler(Stage stage){
     //TODO trying to make the content change when when buttons are clicked

     HomePane mainPane2=new HomePane(stage,new WorkingTodayContent(stage));

     stage.getScene().setRoot(mainPane2); 
//  System.out.println(mainPane2.content);

 }

首先是:

HomePane myPane = new HomePane(primaryStage,new HomeContent(primaryStage));

    Scene homeScene = new Scene (myPane);

    primaryStage.setMinHeight(1000);
    primaryStage.setMinWidth(1700);

    primaryStage.setScene(homeScene);


    primaryStage.show();

2 个答案:

答案 0 :(得分:0)

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

示例

@FunctionalInterface
public interface NodeReplaceListener {

    public void onNodeReplace();

}
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获取来自其他线程的更新:

Platform.runLater(() -> signIn.setText("Scan your card to sign in/out"));

答案 1 :(得分:0)

正如你所说:

  

有些窗格有后台线程,但是它们运行。

我假设您在自己的Pane实现中创建了这些线程。我将向您展示一个带有抽象类的解决方案,每个窗格(或者至少是作为根插入的窗格)都应该扩展。如果你没有,我强烈建议你这样做。虽然你没有提供关于这些窗格的大量信息,所以我将把你的答案集成到你的代码中(如果你决定遵循它)。

public abstract class OwnPane extends Pane {
    protected volatile boolean isRoot = false;

    public void setAsRoot(){
        isRoot = true;
    }

    public void unsetAsRoot(){
        isRoot = false;
    }
}

我稍后会回到volatile关键字。

现在您可以在OwnPane - 方法中创建您的主题,最好是在activateThread或其子类中,例如:

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关键字,我们确保所有线程都能访问相同的&#34;变量&#34; (出于性能原因,每个线程都有自己的版本) 线程在OwnPane(或子类)中的方法中创建的事实允许从isRoot对象中访问Runnable字段。在OwnPane的子类中,您甚至可以覆盖setAsRoot方法,以便在调用setAsRoot方法时(如果需要)直接启动NFS线程:

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