JavaFX - 线程挂起,我不知道如何在 UI 线程之外 运行 我的循环

JavaFX - Thread hangs, and I can't figure out how to run my loop outside the UI thread

朋友。此处是 Whosebug 的新手,也是 JavaFX 的新手。如果我弄乱了任何命名法并且这有点冗长,我深表歉意,但我确实将我的代码缩减为最小的 "working" 示例。 (下面的代码挂起,除非你注释掉 while 循环,正如代码本身所提到的)

我想弄清楚如何让 Slider 在单击 "wake" 按钮后连续更改其值并在单击 "sleep" 按钮后停止更改值。为了争论起见,假设它将值随机更改为 0 到 99 之间的整数。

从我read so far(我读了很多书,但这是最近的两个例子)看来我应该可以使用Platform.runLater()updateMessenger() 以避免 运行 在 UI 线程上无限循环直到我的睡眠按钮中断。前者至今没能实现,后者我也不知道怎么实现。

我还尝试了解 属性 绑定,以便我可以在 UI 线程中进行 Slider.valueProperty 更改(更改 UI 线程中的 UI 元素=] 线程是有道理的......对吧?)同时更改一个中间变量,我称之为 state 并在后台线程上从 class stateObject 创建。在后台线程计算出我想赋予滑块的值后,它会更改 state 的值,该值绑定到 Slider 的值。我认为将值计算分离到后台线程中,并将 Slider 值更改到 UI 线程中可以解决问题,但我一直遇到挂断或臭名昭著的 IllegalStateException 异常,Not on FX application thread

我不完全确定 Platform.runLater() 魔法应该如何工作,但我仍然无法确定我是否 运行 我的 运行nable 接口在里面或者Thread().

任何关于为什么这些尝试 failed/how 使事情正常进行的帮助或解释将不胜感激。

干杯, 麦克

import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.control.*;
import javafx.geometry.*;

//Not sure which of these, if any, I actually need
import javafx.event.*;
import javafx.concurrent.*;
import javafx.beans.property.*;

public class MWE extends Application {

    //Main method
    public static void main(String[] args) {
        launch(args);
    }

    //Class fields
    Button sleepBtn, wakeBtn;
    Slider displaySldr;
    boolean wakeful = true;

    //state property class
    class stateObject {
        //Store the property
        private DoubleProperty state = new SimpleDoubleProperty();
        //Define getter for property value
        public final double getState() {
            return state.get();
        }
        //Define setter for property value
        public final void setState(double val) {
            state.setValue(val);
        }
        //Define getter for property itself
        public DoubleProperty stateProperty() {
            return state;
        }
    }

    //Create state
    stateObject state = new stateObject();

    //Start method
    @Override
    public void start(Stage primaryStage) {

        //Create top-level pane to store nodes into for the scene
        VBox mainPane = new VBox(20);
        mainPane.setAlignment(Pos.CENTER);

        //Create buttons
        sleepBtn = new Button("Sleep");
        sleepBtn.setOnAction(e -> NPSleep());
        wakeBtn = new Button("Wake");
        wakeBtn.setOnAction(e -> NPWake());

        //Lay out buttons
        VBox buttonsBox = new VBox(10);
        buttonsBox.getChildren().addAll(wakeBtn,sleepBtn);
        buttonsBox.setAlignment(Pos.CENTER);

        //Create slider to be change continuously by clicking wakeBtn once
        //Slider should stop changing when sleepBtn is clicked
        displaySldr = new Slider(0,100,50);
        displaySldr.setMajorTickUnit(25);
        displaySldr.setMinorTickCount(24);
        displaySldr.setSnapToTicks(true);
        displaySldr.setShowTickMarks(false);
        displaySldr.setShowTickLabels(true);

        //Make property event. When 'state' changes, change displaySldr value
        state.stateProperty().addListener(
            (observable,oldvalue,newvalue) ->
                    displaySldr.setValue(newvalue.intValue())
        );


        //Organize labels and sliders
        VBox LSPane = new VBox(10);
        LSPane.getChildren().addAll(displaySldr);
        LSPane.setAlignment(Pos.CENTER);

        //Organize sub-panes into top-level pane, mainPane
        mainPane.getChildren().addAll(LSPane,buttonsBox);

        //Set the scene
        Scene mainScene = new Scene(mainPane);

        //Set the stage
        primaryStage.setTitle("oh god please work");
        primaryStage.setScene(mainScene);
        primaryStage.show();

    }

    private void NPWake() {

        Runnable wakeyWakey = () -> {
            while (wakeful == true) { //Commenting out the while loop and 
                                        //leaving the command allows program to run fine
                                        //but slider changes only once, not continuously
                state.setState( Math.floor(Math.random()*100) );

                try{
                    Thread.sleep(300);
                } catch (InterruptedException ie) {
                }
            }
        };

        Platform.runLater(wakeyWakey);
//        Thread wakeThread = new Thread(wakeyWakey); //
//        wakeThread.start();                         // This doesn't work either
    }


    private void NPSleep() {
        if (wakeful == true) {
            wakeful = false;
        }
    }

}

您不需要 State Object,因为您仍然必须更新 JavaFX Application Thread 上的值,所以它打败了创建此类对象的想法。您可以删除所有 State Object 相关代码。下面的代码是您的解决方案的修复:

private void NPWake() {
    Thread t = new Thread(() -> {
        while (wakeful) {
            try {
                Platform.runLater(() -> {
                    displaySldr.setValue(Math.floor(Math.random() * 100));
                });

                Thread.sleep(300);
            }
            catch (Exception e) {}
        }
    });
    t.start();
}
Button

onActionJavaFX Application Thread 上处理,在任何情况下我们都不想在该线程上有一个无限循环 运行 除非它是它自己的内部,因为循环只会挂起该线程。因此,我们创建了一个 new Thread t 它将具有该循环,并且在其主体中将调用 JavaFX Application Thread 以提交更改。如您所知,对 UI / JavaFX 属性的更改必须发生在 JavaFX Application Thread 上。 Platform.runLater( "some code" ) 意味着 "some code" 将在将来的某个时间点在 JavaFX Application Thread 上执行(几乎是立即,如果线程不忙)。

关于此类应用程序的设计,我建议您看看AnimationTimer - https://docs.oracle.com/javase/8/javafx/api/javafx/animation/AnimationTimer.html or Timeline - https://docs.oracle.com/javase/8/javafx/api/javafx/animation/Timeline.html。他们都 运行 JavaFX Application Thread.