【问题标题】:new Thread, application still running after Stage-close新线程,应用程序在阶段关闭后仍在运行
【发布时间】:2015-04-22 01:11:31
【问题描述】:

所以我遵循了这个教程: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();
}

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

第二件事是,当我尝试为 Label 设置文本时,我得到了错误:

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)

...是的,我将阅读有关线程的更多信息,但我想知道这些问题的答案。

【问题讨论】:

    标签: multithreading javafx-8


    【解决方案1】:

    第一部分

    线程在创建时独立于其他线程运行。你有一个新线程,它有一个无限循环,这意味着它会一直运行下去,即使在舞台关闭之后也是如此。

    通常不建议使用无限循环,因为要跳出来非常困难。

    建议您使用:

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

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

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

    JavaFX API 的 (感谢 James_D 的评论)

    这些不需要显式取消,因为ScheduledService 使用守护线程,AnimationTimer 在 JavaFX 线程上运行。

    第二部分

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

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

    由于您创建了一个新线程并尝试更新 label,它是一个 JavaFX 节点,它会引发异常。欲了解更多信息,请访问:

    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

    【讨论】:

    • 如果您愿意,您仍然可以使用长时间运行的线程;只需在启动前调用setDaemon(true);即可;那么它不会阻止应用程序关闭。不过,我建议使用某种高级调度 API;另见ScheduledServiceAnimationTimer。我至少会建议您的线程在每次迭代时休眠一段时间,这样它就不会消耗尽可能多的 CPU。
    • @James_D 我突然想到还有 JavaFX API。谢谢,我会将它们添加到答案中。虽然,setDaemon(true) 是一个解决方案,但我不建议用户在不了解它的作用的情况下使用它。
    • 所以我也可以声明 boolean close=false;while(true && (close =! false){...stage.setOnCloseRequest(closeEvent -> { close = true; }); 我会检查链接。
    • 不,你不能因为 lambda 中使用的变量必须是 finaleffectively final。尽管您可以将 AtomicBoolean 用于您的用例,但您不需要 true 在里面。只需确保让线程休眠一段时间,以避免@James_D 建议的不必要的 CPU 使用。同样,强烈建议使用解决方案中建议的高级 API。
    • 如果您使用ScheduledServiceAnimationTimer,则无需明确停止它们。 ScheduledService 默认使用守护线程,因此它们不会阻止应用程序关闭。 AnimationTimer 只在 FX 应用程序线程上执行。我认为要显示时钟,我会使用AnimationTimer
    【解决方案2】:

    就我而言,ScheduledExecutorService 你不能轻易将它设置为守护进程,我不想和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--;
        }
    }
    

    【讨论】:

    • 很高兴看到有人评估选项,向他们学习并提出自己有用的独立答案。
    猜你喜欢
    • 2013-02-03
    • 2020-01-03
    • 2011-04-02
    • 2021-05-17
    • 2021-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多