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

时间:2016-08-02 09:17:55

标签: java multithreading swing javafx illegalstateexception

通过将应用程序从Swing迁移到JavaFX,我遇到了问题。

为了使它尽可能简单,在我的应用程序开始时,JavaFX Stage在主JavaFX类(Main.java)中初始化。在初始化结束时(在JFX线程中),我打开一个Swing customed 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:重要的是我通过调用JDialog来打开openSlpDlg(),它将要求用户选择一个设备,并且将返回选择的结果在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>

来自外部应用程序的

MainGUIcon.getTabbedPane()中的JavaFX主应用程序调用的update()(在上面的代码中)将创建一个新的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

问题是,我需要存储在userInfo中的登录信息以继续在构造函数中构建我的JPanel(所以在AWT EventQueue线程中)但我的LoginDlg是JavaFX实现的。我尝试使用Platform.runLater(new Runnable(){...}),但它不起作用,这是正常的,因为我目前没有使用JavaFX线程,并且它不会立即在FX线程中执行(对话框永远不会显示,登录无法完成,所以JPanel将为null并显示一条警告消息,告知无法连接)。

我已经在stackoverflow上看到这个例外的帖子很少,但我认为我的情况有点棘手,我找不到一个很好的解决方案来克服这个问题。

希望你能帮助我。如果您需要更多详细信息,请询问!

1 个答案:

答案 0 :(得分:0)

混合Swing和JavaFX很棘手,因为每个工具包都是单线程的,并且每个工具包都有自己的UI线程,其中&#34; live&#34;必须管理UI元素。对于Swing,请参阅"Swing's Threading Policy" in the javax.swing package documentation;对于JavaFX,请参阅"Threading" section of the Application documentation。如果违反它们,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 UI无响应,并可能导致糟糕的用户体验。构建应用程序通常更好,这样MainGUI可以在没有登录完成的情况下显示,然后在登录完成时更新;或者在完成登录之前根本不调用MainGUI构造函数。