Platform.runLater() 是如何运作的?

How does Platform.runLater() function?

我有一个简单的应用程序,它在后台更新数据,在更新时,它会禁用所有其他按钮并启用 TextArea 来显示进度。

步骤:

  1. 禁用主界面中的所有其他按钮(按钮名称:plotButton)

  2. 启用显示更新已开始的文本区域(文本区域名称:infoLogTextArea)

  3. 然后只启动update方法(update()抛出异常)

代码如下:

@FXML
    public void handleUpdateButton() {
        
        infoLogTextArea.setVisible(true);
        infoLogTextArea.appendText("Please wait while downloading data from internet.....\n");      
        plotButton.setDisable(true);
        updateButton.setDisable(true);
            
        if(c!=null) {
            Runnable task = new Runnable() {
                @Override
                public void run() {
                    // Thread.sleep(10000); -> sleep for 10secs
                    Platform.runLater(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                c.updateData();
                                infoLogTextArea.appendText(c.getErrorLog().toString());
                                plotLabel.setText(c.getCityData().size()+" cities found and updated from internet");
                                infoLogTextArea.appendText("Successfully updated the data from Internet\n");
                            }catch (IOException e) {
                                infoLogTextArea.setText("Couldnot update the data from web: "+e.getMessage()+"\n");
                            }
                            finally {
                                plotButton.setDisable(false);
                                updateButton.setDisable(false);
                            }
                        }
                    });
                }
            };
            
            new Thread(task).start();
            
        }else {
            System.out.println("c not initialized");
        }
    }

现在代码运行良好,但有时步骤 1 和 2 未执行,它开始步骤 3(更新),这可能会冻结程序。 如果我在第 2 步和第 3 步之间放置 Thread.sleep(10 秒),那么它 完全没问题 。 (代码中有注释)

但是谁能解释一下幕后发生的事情以及为什么 Platform.runLater() 总是不起作用?

平台的文档class 很好地解释了一切:

public static void runLater(Runnable runnable)

Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted. A runnable passed into the runLater method will be executed before any Runnable passed into a subsequent call to runLater. If this method is called after the JavaFX runtime has been shutdown, the call will be ignored: the Runnable will not be executed and no exception will be thrown. NOTE: applications should avoid flooding JavaFX with too many pending Runnables. Otherwise, the application may become unresponsive. Applications are encouraged to batch up multiple operations into fewer runLater calls. Additionally, long-running operations should be done on a background thread where possible, freeing up the JavaFX Application Thread for GUI operations.

This method must not be called before the FX runtime has been initialized. For standard JavaFX applications that extend Application, and use either the Java launcher or one of the launch methods in the Application class to launch the application, the FX runtime is initialized by the launcher before the Application class is loaded.

因此,使用 runLater 仅更新非 JavaFX 线程上的任何 UI 元素,并将任何繁重的工作留在后台线程上。

JavaFX 应用程序 运行 在应用程序线程上,它处理所有 UI 元素。这意味着如果您单击按钮 A 并单击该按钮启动需要 5 秒完成的方法 A,然后在单击该按钮一秒钟后,您尝试单击启动方法 B 的按钮 B,方法 B 将不会启动,直到方法A完成。或者可能按钮 B 在方法 A 完成之前甚至无法工作,我对那里的细节有点模糊。

阻止应用程序冻结的一个好方法是使用线程。要解决上述情况,单击按钮 A 将启动启动新线程的方法 A。然后线程可以在不锁定 UI 并阻止您单击按钮 B 的情况下完成它想要完成的时间。

现在,假设方法 A 中的某些内容需要在应用程序线程上,例如,它更新了一个 UI 组件,如 Label 或 TextField。然后在方法 A 中的线程中,您需要将影响 UI 的部分放入 Platform.runLater(),这样它将 运行 在应用程序线程上与其余 UI.

这对于您的示例意味着您有两个选择。
1. 根本不要使用线程,因为您不希望用户在更新发生时与 UI 交互。
2. 像这样将 c.updateData()Platform.runLater() 中移出:

 Runnable task = new Runnable() {
            @Override
            public void run() {
                c.updateData();
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            infoLogTextArea.appendText(c.getErrorLog().toString());
                            plotLabel.setText(c.getCityData().size()+" cities found and updated from internet");
                            infoLogTextArea.appendText("Successfully updated the data from Internet\n");
                        }catch (IOException e) {
                            infoLogTextArea.setText("Couldnot update the data from web: "+e.getMessage()+"\n");
                        }
                        finally {
                            plotButton.setDisable(false);
                            updateButton.setDisable(false);
                        }
                    }
                });
            }
        };

其中任何一个都可以工作,但你现在正在做的是你在应用程序线程上,然后你启动另一个线程,其唯一目的是 运行 应用程序线程上的东西.