为什么线程会阻塞我的JavaFX UI线程?
当用户选择在另一个线程中启动阻止进程的菜单项时,我试图在JavaFX 8应用程序中提供反馈。 在我的实际应用程序中,它是一个文件下载,但我通过示例使用最少的代码创建了一个测试用例:
import javafx.application.Application; import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.control.MenuButton; import javafx.scene.control.ToolBar; import javafx.scene.control.MenuItem; import javafx.stage.Stage; public class BlockingThreadTestCase extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) throws Exception { MenuItem menuItem = new MenuItem("Start"); MenuButton menuButton = new MenuButton(); menuButton.setText("Async Process"); menuButton.getItems().addAll(menuItem); menuItem.setOnAction(event -> { menuButton.setText("Running..."); Platform.runLater(() -> { try { // Simulate a blocking process Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } menuButton.setText(menuButton.getText() + "Done!"); }); }); final ToolBar toolbar = new ToolBar(menuButton); final Scene scene = new Scene(toolbar); primaryStage.setScene(scene); primaryStage.setWidth(150); primaryStage.show(); } }
这是它应该如何工作:当您选择“开始”菜单项时,主菜单文本应立即更改为“正在运行…”,然后它应附加“完成!” 在模拟我的文件下载的5秒睡眠之后。
实际发生的是,即使我使用的是Platform.runLater()
,文本更新也会在阻塞过程完成后触发。 我究竟做错了什么?
最简单的方法是使用Task
。 只有在需要从其他线程更新UI时才需要Platform.runLater
,因此在您的情况下不需要。 如果要在后台任务运行时跟踪后台任务的进度,可以在任务中使用updateMessage
和updateProgress
方法将消息安全地传递给UI线程,而不必担心通过EDT进行调度。 您可以在https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm找到更多相关信息。
请参阅下面的最小工作示例。
import javafx.application.Application; import javafx.concurrent.Task; import javafx.scene.Scene; import javafx.scene.control.MenuButton; import javafx.scene.control.MenuItem; import javafx.scene.control.ToolBar; import javafx.stage.Stage; public class BlockingThreadTestCase extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) throws Exception { MenuItem menuItem = new MenuItem("Start"); MenuButton menuButton = new MenuButton(); menuButton.setText("Async Process"); menuButton.getItems().addAll(menuItem); menuItem.setOnAction(event -> { menuButton.setText("Running..."); Task task = new Task() { @Override public Void call() { //SIMULATE A FILE DOWNLOAD try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }; task.setOnSucceeded(taskFinishEvent -> menuButton.setText(menuButton.getText() + "Done!")); new Thread(task).start(); }); final ToolBar toolbar = new ToolBar(menuButton); final Scene scene = new Scene(toolbar); primaryStage.setScene(scene); primaryStage.setWidth(150); primaryStage.show(); } }