新线程,应用程序在阶段关闭后仍在运行



所以我遵循了本教程:https://www.youtube.com/watch?v=gyyj57O0FVI

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

关闭窗口后,应用程序/线程仍在系统中运行。我想这是因为无限循环,但线程不应该随着应用程序的关闭而终止吗?

第二件事是,当我试图设置标签的文本时,我会得到错误:

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)

是的,我将阅读更多关于线程的内容,但我想知道这些问题的答案。

第一部分

线程在创建时独立于其他线程运行。你有一个新的线程,它有一个无限循环,这意味着它将永远运行,即使在阶段关闭之后。

通常,不建议使用无限循环,因为很难突破它。

建议您使用:

  • 计时器任务
  • ScheduledExecutiorService

然后,您可以调用其中任何一个(基于您正在使用的内容)

  • 计时器任务取消()
  • ScheduledExecutiorService.shutdownNow()

当你的舞台关闭时。你可以使用类似的东西:

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

JavaFX API的(感谢James_D的评论)

由于ScheduledService使用守护进程线程,而AnimationTimer在JavaFX线程上运行,因此不需要显式取消这些操作。

  • ScheduledService
  • 动画计时器

第二部分

你的问题的第二部分已经在论坛上得到了一次又一次的回答。

您需要在JavaFX应用程序线程上才能使用场景图元素。

由于您已经创建了一个新线程并试图更新label,这是一个JavaFX节点,所以它抛出了异常。欲了解更多信息,请访问:

尝试删除形状时发生JavaFX错误

为什么我得到java.lang.IllegalStateException"不在FX应用程序线程上";在JavaFX上?

使用计时器时,Javafx不在fx应用程序线程上

就我而言,使用ScheduledExecutorService,你不能轻易地将其设置为deamon,我也不想使用stage.setOnCloseRequest(closeEvent -> {});

使用AnimationTimer,我不能像你建议的那样在迭代之间做Thread.sleep(100)之类的事情,因为"AnimationTimer在JavaFX线程上运行。"

ScheduledService对我来说现在很难理解。。。

因此,当我不断阅读时,我得出结论,也许这个简单的选择是最好的:

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

感谢James_DItachiUchiha。它有效,如果我缺少什么,请告诉我!

编辑:我还包括了倒计时的代码,因为这是我最初的目标,也许有人会发现它也很有用:

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

最新更新