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
的 onAction
在 JavaFX 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
.
朋友。此处是 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
的 onAction
在 JavaFX 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
.