簡體   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