new Thread, application still running after Stage-

2019-02-19 14:24发布

So I followed this tutorial: https://www.youtube.com/watch?v=gyyj57O0FVI

and I made exactly the same code in javafx8.

public class CountdownController implements Initializable{

@FXML
private Label labTime;

@Override
public void initialize(URL location, ResourceBundle resources) {

    new Thread(){
        public void run(){
            while(true){
                Calendar calendar = new GregorianCalendar();
                int hour = calendar.get(Calendar.HOUR);
                int minute = calendar.get(Calendar.MINUTE);
                int second = calendar.get(Calendar.SECOND);
                String time = hour + ":" + minute + ":" + second;

                labTime.setText(time);
            }

        }
    }.start();
}

After I close the Window, application/thread is still running in the system. My guess its because the infinite loop, but shouldnt the thread be terminated with application closing?

Second thing is that when I try to set the text for Label I get the error:

Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:204)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:364)
    at javafx.scene.Parent$2.onProposedChange(Parent.java:364)
    at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
    at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
    at com.sun.javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:575)
    at com.sun.javafx.scene.control.skin.LabeledSkinBase.handleControlPropertyChanged(LabeledSkinBase.java:204)
    at com.sun.javafx.scene.control.skin.LabelSkin.handleControlPropertyChanged(LabelSkin.java:49)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$60(BehaviorSkinBase.java:197)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$$Lambda$144/1099655841.call(Unknown Source)
    at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(MultiplePropertyChangeListenerHandler.java:55)
    at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
    at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:103)
    at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:110)
    at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:143)
    at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
    at javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
    at javafx.scene.control.Labeled.setText(Labeled.java:146)
    at application.CountdownController$1.run(CountdownController.java:29)

...yes, I am going to read more about threads, but I would like to know the answer to these questions.

2条回答
看我几分像从前
2楼-- · 2019-02-19 14:49

Part I

A thread, when created, runs independent of other threads. You have a new thread which has an infinite loop, which implies, it will keep running forever, even after the stage has been closed.

Normally, using a infinite loop is not advised, because breaking out of it is very difficult.

You are advised to use :

You can then call either one of them (based on whatever you are using)

when your stage is closed. You can use something like :

stage.setOnCloseRequest(closeEvent -> {
       timertask.cancel();  
});  

JavaFX API's (thanks to James_D comment's)

These do not need to be explicitly canceled as ScheduledService uses daemon threads and AnimationTimer runs on the JavaFX thread.

Part II

Your second part of the question has been answered time and again in the forum.

You need to be on the JavaFX Application thread to use scene graph elements.

Since you have created a new thread and trying to update label, which is a JavaFX node, it throws the exception. For more information, please visit:

JavaFX error when trying to remove shape

Why am I getting java.lang.IllegalStateException "Not on FX application thread" on JavaFX?

Javafx Not on fx application thread when using timer

查看更多
对你真心纯属浪费
3楼-- · 2019-02-19 14:52

With ScheduledExecutorService as far as I am concerned You cant easly set it as deamon and I don't want to play with stage.setOnCloseRequest(closeEvent -> {});

With AnimationTimer I cant do something like Thread.sleep(100) beetween iteration like you suggested because "AnimationTimer runs on the JavaFX thread."

ScheduledService is just quite difficult for me to understand right now...

so, as I was reading and reading about it I came to conclusion that maybe this simple option will be the best:

public class CountdownController implements Initializable{

@FXML
private Label labTime;
@FXML
private Button buttSTOP;

@Override
public void initialize(URL location, ResourceBundle resources) {
     Timer timer = new Timer(true); //set it as a deamon
     timer.schedule(new MyTimer(), 0, 1000);
}


public class MyTimer extends TimerTask{
    @Override
    public void run() {
        Calendar calendar = new GregorianCalendar();
        int hour = calendar.get(Calendar.HOUR);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);
        String time = hour + ":" + minute + ":" + second;

        Platform.runLater(() -> {
            labTime.setText(time);
        });

    }
}

Thanks James_D and ItachiUchiha. It works, let me know if I'am something missing!

EDIT: I also include code for Counting down the time, as it was my initial aim, maybe someone will find it usefull as well:

public class CountdownController implements Initializable{

@FXML
private Label labTime;
@FXML
private Button buttSTOP;

private Timer timer = new Timer(true); //set it as a deamon
private int iHours = 0,
            iMinutes = 1,
            iSeconds = 10;  


public void initCountdownController(int iHours, int iMinutes, int iSeconds){
    this.iHours = iHours;
    this.iMinutes = iMinutes;
    this.iSeconds = iSeconds;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
    buttSTOP.setOnAction(e -> {
        buttSTOPAction(e);
    });
    timer.schedule(new MyTimer(), 0, 1000);
}
private void buttSTOPAction(ActionEvent e) {
    timer.cancel();
}
public class MyTimer extends TimerTask{
    @Override
    public void run() {
        String time = iHours + ":" + iMinutes + ":" + iSeconds;
        Platform.runLater(() -> {
            labTime.setText(time);
        });

        if(iSeconds < 1)
            if(iMinutes < 1)
                if(iHours < 1)
                    this.cancel();
                else{
                    iHours--;
                    iMinutes = 59;
                    iSeconds = 59;
                }
            else{
                iMinutes--;
                iSeconds = 59;
            }
        else
            iSeconds--;
    }
}
查看更多
登录 后发表回答