繁体   English   中英

JavaFX中的Java AWT EventQueue“不在FX应用程序线程上”异常

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM