[英]ERROR! Exception in thread “AWT-EventQueue-0” java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0
[英]Java AWT EventQueue “Not on FX application thread” exception in JavaFX
通过将应用程序从Swing迁移到JavaFX,我遇到了问题。
为了使它尽可能简单,在我的应用程序开始时,在主要JavaFX类(Main.java)中初始化了JavaFX Stage
。 在初始化结束时(在JFX线程中),我打开了一个Swing定制的JDialog
(目前尚未迁移),要求用户选择设备。 在此JDialog
我有一个ObservableModel
,它从用户在设备选择JDialog
发送作为参数条目。 我的主要JavaFX类是一个Observer
,我通过重写update()
函数从所选设备中获取条目。 到目前为止,一切都很好 。
然后,仍然在update()
函数中,如果用户未连接,我想打开一个在JavaFX中创建的登录对话框(在迁移之前为Swing JDialog
)。 当我尝试打开它时,我得到一个java.lang.IllegalStateException
异常,告诉"Not on FX application thread; currentThread = AWT-EventQueue-0"
现在一些代码:
JavaFX Main
类Main.java:重要的是,我通过调用openSlpDlg()
打开JDialog
,这将要求用户选择设备,选择的结果将作为参数返回到update()
方法中。
public class Main extends Application implements Observer {
@Override
public void start(Stage stage) {
// init stage
stage.setTitle(Messages.getString("Appli_HaslerST"));
stage.getIcons().add(new Image(getClass().getResourceAsStream("../gifs/application.gif")));
mainContainer = new BorderPane();
Scene root = new Scene(mainContainer, 1200, 1000);
stage.setScene(root);
// init stage content
initialize();
stage.show();
// open that device selection dialog
openSlpDlg();
this.stage = stage;
}
public static void main(String[] args) {
launch(args);
}
@Override
public void update(Observable o, Object arg) {
// called when user has chosen a device in the JDialog
if (arg instanceof Object[]) {
Object[] set = (Object[]) arg;
try {
// con is an Interface that will create the correct
// device JPanel, depending of selection.
// the JPanel is created in constructor of MainGUI
// Start method of device application which creates a
// new MainGUI (JPanel)
// AbstractTabbedPanel is a customed JPanel
AbstractTabbedPanel panel = con.getTabbedPanel();
} catch (Exception e) {
}
}
}
}
外部设备应用程序返回一个JPanel
,它将使用SwingNode
存储在我的舞台中(工作正常)。 但是在创建此JPanel
,在MainGUI
(如上面的代码中的注释中所述),我检查用户是否已登录,如果没有,则打开登录对话框,它是JavaFX Dialog<R>
来自外部应用程序的MainGUI
。 由JavaFX Main应用程序在update()
con.getTabbedPane()
调用的con.getTabbedPane()
(在上面的代码中)将创建一个新的MainGUI
。
public class MainGUI extends JPanel {
public MainGUI() {
// [...] all initalization done in constructor
// test if user connected
if (LocalAccessControl.getInstance().getAccessControl() == null) {
// not connected case
// JavaFX custom login extends from Dialog<LoginInfo>
LoginDlg login;
login = new LoginDlg()
// show login dialog and get a LoginInfo object with
// user info
LoginInfo userInfo = login.showLogin();
}
}
}
并通过LoginInfo userInfo = login.showLogin();
我收到此异常: java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0
java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0
问题是,我需要存储在userInfo
登录信息才能继续在构造函数中构造JPanel
(因此在AWT EventQueue线程中),但是LoginDlg是JavaFX实现的。 我尝试使用Platform.runLater(new Runnable(){...})
但是它不起作用,而且很正常,因为我当前未使用JavaFX线程,并且不会在FX线程中立即执行(对话框永远不会显示并且无法完成登录,因此JPanel将为空,并显示警告消息,提示无法建立连接。
我已经在stackoverflow上看到过几篇关于此异常的文章,但我认为我的案例比较棘手,我找不到解决此问题的好方法。
希望您能够帮助我。 如果您需要更多详细信息,请询问!
将Swing和JavaFX混合使用非常棘手,因为每个工具箱都是单线程的,并且每个都有自己的UI线程,必须在该线程上管理“实时” UI元素。 对于Swing,请参见javax.swing软件包文档中的“ Swing的线程策略” 。 对于JavaFX,请参阅Application
文档的“线程”部分 。 JavaFX尝试在可能的情况下通过抛出运行时异常来强制执行这些规则(如您所见); Swing不会引发异常。 在这两种情况下,违反线程规则都有可能使您的应用程序处于不确定状态,并在应用程序生命周期中的任意时间导致错误。
您可以使用SwingUtilities.invokeLater(() -> { ... });
调用AWT事件分发线程上的代码,然后使用Platform.runLater(() -> { ... });
调用FX应用程序线程上的代码。 这些调用是异步的 ,因此不会立即执行; 换句话说,紧随这些调用之后的代码很可能在提交给UI线程的代码之前执行。
此外,除非明确设计了这些阻止调用,否则不应在UI线程上进行阻止调用。 例如,如果dialog
是一个FX对话框(或阶段),则dialog.showAndWait()
是一个阻塞调用,旨在安全地在FX Application线程上执行; 但是,您不应在AWT事件分发线程上进行阻止以阻止FX对话框输入的调用。
您尚未在代码中显示完整的示例,但看起来您需要以下内容:
@Override
public void start(Stage stage) {
// init stage
stage.setTitle(Messages.getString("Appli_HaslerST"));
stage.getIcons().add(new Image(getClass().getResourceAsStream("../gifs/application.gif")));
mainContainer = new BorderPane();
Scene root = new Scene(mainContainer, 1200, 1000);
stage.setScene(root);
// init stage content
initialize();
stage.show();
// open that device selection dialog: this is a Swing dialog,
// so it must be performed on the AWT event dispatch thread:
SwingUtilities.invokeLater(this::openSlpDlg);
this.stage = stage;
}
您的update()
方法是作为对AWT事件分派线程中用户输入的响应而执行的(我假设是从openSlpDlg
显示的Swing对话框中的操作侦听器执行); 因此,它及其调用的方法(例如new MainGUI()
在AWT事件分配线程上执行。 因此,您现在需要:
public MainGUI() {
// this method is executed on the AWT event dispatch thread
// [...] all initalization done in constructor
// test if user connected
if (LocalAccessControl.getInstance().getAccessControl() == null) {
// not connected case
// JavaFX custom login extends from Dialog<LoginInfo>
// Tricky part. We need to create a JavaFX dialog (so must be
// done on the FX Application Thread), show it and wait for the result
// (so make a blocking call, which needs to be on a background thread),
// and process the result back on the AWT thread.
// task to execute on the FX Application Thread, returning a LoginInfo:
FutureTask<LoginInfo> getLoginTask = new FutureTask<>(() -> {
LoginDlg login;
login = new LoginDlg()
// show login dialog and get a LoginInfo object with
// user info
return login.showLogin();
});
// execute the task on the FX Application Thread:
Platform.runLater(getLoginTask);
// now create a background thread that waits for the login task to complete,
// and processes the result back on the AWT event dispatch thread:
Thread waitForLoginThread = new Thread(() -> {
try {
final LoginInfo userLogin = getLoginTask.get();
SwingUtilities.invokeLater(() -> {
// process userLogin here...
});
} catch (InterruptedException exc) {
throw new Error("Unexpected interruption waiting for login");
} catch (ExecutionException exc) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Error getting login info", exc);
}
});
waitForLoginThread.start();
}
}
如果您确实要阻塞MainGUI
构造函数,直到关闭FX对话框,则可以通过等待提交给FX Application Thread的任务直接在那里完成(而不是在后台线程中)来完成:
public MainGUI() {
// this method is executed on the AWT event dispatch thread
// [...] all initalization done in constructor
// test if user connected
if (LocalAccessControl.getInstance().getAccessControl() == null) {
// not connected case
// JavaFX custom login extends from Dialog<LoginInfo>
// Tricky part. We need to create a JavaFX dialog (so must be
// done on the FX Application Thread), show it and wait for the result
// (so make a blocking call, which needs to be on a background thread),
// and process the result back on the AWT thread.
// task to execute on the FX Application Thread, returning a LoginInfo:
FutureTask<LoginInfo> getLoginTask = new FutureTask<>(() -> {
LoginDlg login;
login = new LoginDlg()
// show login dialog and get a LoginInfo object with
// user info
return login.showLogin();
});
// execute the task on the FX Application Thread:
Platform.runLater(getLoginTask);
// wait for task submitted to FX Application Thread to complete.
// note this blocks the AWT event dispatch thread:
try {
final LoginInfo userLogin = getLoginTask.get();
// process userLogin here...
} catch (InterruptedException exc) {
Thread.currentThread().interrupt();
} catch (ExecutionException exc) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Error getting login info", exc);
}
}
}
我不推荐这种方法:它将使Swing用户界面无响应,并可能导致不良的用户体验。 通常最好对应用程序进行结构设计,以便可以在未完成登录的情况下显示MainGUI
,然后在登录完成后进行更新。 否则将MainGUI
在登录完成之前根本不调用MainGUI
构造函数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.