更改根节点时后台线程不会关闭 [javaFX]
Background Threads not closing when root node is changed [javaFX]
我正在创建一个 javafx 程序,我的程序导航菜单通过更改场景的根来工作。根全部继承自 Pane class。一些窗格具有后台线程,它们 运行。但是,当事件处理程序更改根窗格时,窗格会切换,但后台线程不会停止。这会导致线程读取 NFC 时出现问题,并导致多个线程尝试从 NFC 读取 reader。
如何关闭后台线程? (从创建它们的窗格外)还是我需要以不同的方式设置线程。
(线程设置为守护进程)。
线程是在 Pane 构造函数中创建的,如下所示:
(我假设因为它们属于该窗格,所以当切换窗格时线程将停止。事实并非如此)。
Runnable r = new Runnable() {
@Override
public void run () {
boolean cont = true;
while(cont){
try {
NFCcard create1 = new NFCcard();
String staffID=create1.getCardID().toString();
staffID = staffID.replaceAll("\D+","");
signIn.setText("Welcome "+getUserName(staffID)+getPhotoSrc(create1.getCardID().toString()));
Thread.sleep(1000);
} catch (CardException e) {
} catch (InterruptedException e) {
}
signIn.setText("Scan your card to sign in/out");
if(getScene().getRoot().isDisable());
cont=false;
}
}};
Thread nfcCheckThread = new Thread(r);
nfcCheckThread.setDaemon(true);
nfcCheckThread.start();
我像这样以静态方式切换窗格:(这些方法在它们自己的 class 中)。
public static void homeButtonhandler(Stage stage){
HomePane mainPane1=new HomePane(stage, new HomeContent(stage));
stage.getScene().setRoot(mainPane1);
}
public static void adminButtonhandler(Stage stage){
DialogBox dialog = new DialogBox();
try{
Optional<String> result = dialog.showAndWait();
if (result.get().equals("115")){
AdminPane adminPane1 = new AdminPane(stage,new Content(stage));
stage.getScene().setRoot(adminPane1);
}}
catch(NoSuchElementException Exception){
}
}
public static void workingTodayButtonhandler(Stage stage){
//TODO trying to make the content change when when buttons are clicked
HomePane mainPane2=new HomePane(stage,new WorkingTodayContent(stage));
stage.getScene().setRoot(mainPane2);
// System.out.println(mainPane2.content);
}
第一次来电是:
HomePane myPane = new HomePane(primaryStage,new HomeContent(primaryStage));
Scene homeScene = new Scene (myPane);
primaryStage.setMinHeight(1000);
primaryStage.setMinWidth(1700);
primaryStage.setScene(homeScene);
primaryStage.show();
您应该添加一种方法来向负责替换场景根的 class 注册侦听器。这允许您收到此类更改的通知并通过终止线程来做出反应。
例子
@FunctionalInterface
public interface NodeReplaceListener {
public void onNodeReplace();
}
private Parent root;
private NodeReplaceListener listener;
private Scene scene;
public void setRoot(Parent root, NodeReplaceListener listener) {
if (root != this.root && this.listener != null) {
this.listener.onNodeReplace();
}
this.root = root;
this.listener = listener;
scene.setRoot(root);
}
@Override
public void start(Stage primaryStage) {
Button btn = new Button("Next Scene");
btn.setOnAction((ActionEvent event) -> {
// replace root
setRoot(new StackPane(new Rectangle(100, 100)), null);
});
StackPane root = new StackPane();
root.getChildren().add(btn);
scene = new Scene(new Group(), 100, 100);
class MyRunnable implements Runnable {
// running volatile to guarantee that visibility of written values to all threads
volatile boolean running = true;
int i;
@Override
public void run() {
while (running) {
System.out.println(i++);
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
}
}
public void cancel() {
running = false;
}
}
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.setDaemon(true);
t.start();
primaryStage.setScene(scene);
// cancel runnable when root is replaced
setRoot(root, r::cancel);
primaryStage.show();
}
顺便说一句:请注意,不应从应用程序线程以外的任何线程对场景进行修改。使用 Platform.runLater
来自不同线程的更新:
Platform.runLater(() -> signIn.setText("Scan your card to sign in/out"));
正如你所说:
Some of the Panes have background threads which they run.
我假设您在自己的 Pane
实现中创建这些线程。我将向您展示一个带有抽象 class 的解决方案,您的每个窗格(或至少作为根插入的窗格)都应该扩展。如果您没有,我强烈建议您这样做。虽然您没有提供有关这些窗格的太多信息,所以我将把我的答案集成到您的代码中(如果您决定遵循它)。
public abstract class OwnPane extends Pane {
protected volatile boolean isRoot = false;
public void setAsRoot(){
isRoot = true;
}
public void unsetAsRoot(){
isRoot = false;
}
}
稍后我会回到 volatile
关键字。
现在您可以创建线程,最好是在 OwnPane
或其子 class 中,例如在 activateThread
方法中:
public void activateNFCThread(){
Runnable r = new Runnable(){
@Override
public void run () {
while(isRoot){
// what the thread has to do ...
}
}
};
Thread nfcCheckThread = new Thread(r);
nfcCheckThread.setDaemon(true);
nfcCheckThread.start();
}
现在我可以解释为什么必须使用 volatile
关键字了:字段 isRoot
将被不同的线程使用。使用 volatile
关键字,我们确保所有线程都将访问相同的 "variable"(否则出于性能原因,每个线程都会有自己的版本)。
事实上,线程是在 OwnPane
(或子class)中的方法中创建的,允许从 Runnable
对象中访问 isRoot
字段。在 OwnPane
的子 classes 中,您甚至可以覆盖 setAsRoot
方法,使 NFS 线程在调用 setAsRoot
方法时直接启动(如果需要):
public class PaneWithNFCReader extends OwnPane {
@Override
public void setAsRoot(){
super.setAsRoot();
activateNFCThread();
}
}
最后,您可以使用这些方法更改舞台中场景的根窗格:
// All your methods regarding stage changes are static, so I'll leave this one static too
public static void changeRoot(Stage stage, OwnPane newRoot){
OwnPane oldStage = (OwnPane)stage.getScene().getRoot();
oldStage.unsetAsRoot();
Platform.runLater(() -> { //Platform.runLater to be sure the main thread that will execute this
//(only main thread is allowed to change something in the JavaFX nodes)
stage.getScene().setRoot(newRoot);
newRoot.setAsRoot();
});
}
我正在创建一个 javafx 程序,我的程序导航菜单通过更改场景的根来工作。根全部继承自 Pane class。一些窗格具有后台线程,它们 运行。但是,当事件处理程序更改根窗格时,窗格会切换,但后台线程不会停止。这会导致线程读取 NFC 时出现问题,并导致多个线程尝试从 NFC 读取 reader。
如何关闭后台线程? (从创建它们的窗格外)还是我需要以不同的方式设置线程。
(线程设置为守护进程)。
线程是在 Pane 构造函数中创建的,如下所示:
(我假设因为它们属于该窗格,所以当切换窗格时线程将停止。事实并非如此)。
Runnable r = new Runnable() {
@Override
public void run () {
boolean cont = true;
while(cont){
try {
NFCcard create1 = new NFCcard();
String staffID=create1.getCardID().toString();
staffID = staffID.replaceAll("\D+","");
signIn.setText("Welcome "+getUserName(staffID)+getPhotoSrc(create1.getCardID().toString()));
Thread.sleep(1000);
} catch (CardException e) {
} catch (InterruptedException e) {
}
signIn.setText("Scan your card to sign in/out");
if(getScene().getRoot().isDisable());
cont=false;
}
}};
Thread nfcCheckThread = new Thread(r);
nfcCheckThread.setDaemon(true);
nfcCheckThread.start();
我像这样以静态方式切换窗格:(这些方法在它们自己的 class 中)。
public static void homeButtonhandler(Stage stage){
HomePane mainPane1=new HomePane(stage, new HomeContent(stage));
stage.getScene().setRoot(mainPane1);
}
public static void adminButtonhandler(Stage stage){
DialogBox dialog = new DialogBox();
try{
Optional<String> result = dialog.showAndWait();
if (result.get().equals("115")){
AdminPane adminPane1 = new AdminPane(stage,new Content(stage));
stage.getScene().setRoot(adminPane1);
}}
catch(NoSuchElementException Exception){
}
}
public static void workingTodayButtonhandler(Stage stage){
//TODO trying to make the content change when when buttons are clicked
HomePane mainPane2=new HomePane(stage,new WorkingTodayContent(stage));
stage.getScene().setRoot(mainPane2);
// System.out.println(mainPane2.content);
}
第一次来电是:
HomePane myPane = new HomePane(primaryStage,new HomeContent(primaryStage));
Scene homeScene = new Scene (myPane);
primaryStage.setMinHeight(1000);
primaryStage.setMinWidth(1700);
primaryStage.setScene(homeScene);
primaryStage.show();
您应该添加一种方法来向负责替换场景根的 class 注册侦听器。这允许您收到此类更改的通知并通过终止线程来做出反应。
例子
@FunctionalInterface
public interface NodeReplaceListener {
public void onNodeReplace();
}
private Parent root;
private NodeReplaceListener listener;
private Scene scene;
public void setRoot(Parent root, NodeReplaceListener listener) {
if (root != this.root && this.listener != null) {
this.listener.onNodeReplace();
}
this.root = root;
this.listener = listener;
scene.setRoot(root);
}
@Override
public void start(Stage primaryStage) {
Button btn = new Button("Next Scene");
btn.setOnAction((ActionEvent event) -> {
// replace root
setRoot(new StackPane(new Rectangle(100, 100)), null);
});
StackPane root = new StackPane();
root.getChildren().add(btn);
scene = new Scene(new Group(), 100, 100);
class MyRunnable implements Runnable {
// running volatile to guarantee that visibility of written values to all threads
volatile boolean running = true;
int i;
@Override
public void run() {
while (running) {
System.out.println(i++);
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
}
}
public void cancel() {
running = false;
}
}
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.setDaemon(true);
t.start();
primaryStage.setScene(scene);
// cancel runnable when root is replaced
setRoot(root, r::cancel);
primaryStage.show();
}
顺便说一句:请注意,不应从应用程序线程以外的任何线程对场景进行修改。使用 Platform.runLater
来自不同线程的更新:
Platform.runLater(() -> signIn.setText("Scan your card to sign in/out"));
正如你所说:
Some of the Panes have background threads which they run.
我假设您在自己的 Pane
实现中创建这些线程。我将向您展示一个带有抽象 class 的解决方案,您的每个窗格(或至少作为根插入的窗格)都应该扩展。如果您没有,我强烈建议您这样做。虽然您没有提供有关这些窗格的太多信息,所以我将把我的答案集成到您的代码中(如果您决定遵循它)。
public abstract class OwnPane extends Pane {
protected volatile boolean isRoot = false;
public void setAsRoot(){
isRoot = true;
}
public void unsetAsRoot(){
isRoot = false;
}
}
稍后我会回到 volatile
关键字。
现在您可以创建线程,最好是在 OwnPane
或其子 class 中,例如在 activateThread
方法中:
public void activateNFCThread(){
Runnable r = new Runnable(){
@Override
public void run () {
while(isRoot){
// what the thread has to do ...
}
}
};
Thread nfcCheckThread = new Thread(r);
nfcCheckThread.setDaemon(true);
nfcCheckThread.start();
}
现在我可以解释为什么必须使用 volatile
关键字了:字段 isRoot
将被不同的线程使用。使用 volatile
关键字,我们确保所有线程都将访问相同的 "variable"(否则出于性能原因,每个线程都会有自己的版本)。
事实上,线程是在 OwnPane
(或子class)中的方法中创建的,允许从 Runnable
对象中访问 isRoot
字段。在 OwnPane
的子 classes 中,您甚至可以覆盖 setAsRoot
方法,使 NFS 线程在调用 setAsRoot
方法时直接启动(如果需要):
public class PaneWithNFCReader extends OwnPane {
@Override
public void setAsRoot(){
super.setAsRoot();
activateNFCThread();
}
}
最后,您可以使用这些方法更改舞台中场景的根窗格:
// All your methods regarding stage changes are static, so I'll leave this one static too
public static void changeRoot(Stage stage, OwnPane newRoot){
OwnPane oldStage = (OwnPane)stage.getScene().getRoot();
oldStage.unsetAsRoot();
Platform.runLater(() -> { //Platform.runLater to be sure the main thread that will execute this
//(only main thread is allowed to change something in the JavaFX nodes)
stage.getScene().setRoot(newRoot);
newRoot.setAsRoot();
});
}