从JavaFX中的不同线程更新UI

我正在开发一个包含多个TextField对象的应用程序,这些对象需要更新以反映关联的后端属性中的更改。 TextField不可编辑,只有后端可能会更改其内容。

据我所知,正确的方法是在一个单独的线程上运行繁重的计算,以免阻塞用户界面。 我使用javafx.concurrent.Task完成了这个javafx.concurrent.Task并使用updateMessage()函数向JavaFX线程传递了一个单值,该工作正常。 但是,我需要更新一个以上的价值,因为后端正在进行更新。

由于后端值是作为JavaFX属性存储的,所以我试着简单地将它们绑定到每个GUI元素的textProperty ,并让绑定完成工作。 然而,这不起作用。 在运行一会儿之后,即使后端任务仍在运行, TextField也会停止更新。 没有例外。

我也尝试使用Platform.runLater()主动更新TextField而不是绑定。 这里的问题在于, runLater()任务的调度速度比平台能够运行得快,所以GUI变得呆滞,即使在后端任务完成之后也需要时间来“赶上”。

我在这里find了几个问题:

logging器条目翻译到用户界面停止更新随着时间的推移

JavaFX中的multithreading挂起UI

但我的问题依然存在。

总结:我有一个后端对属性进行更改,我希望这些更改显示在GUI上。 后端是一个遗传algorithm,所以它的操作被分解成几代。 我希望TextField在两代之间至less刷新一次,即使这会延误下一代。 更重要的是GUI比GA运行得更快。

如果我没有清楚问题,我可以发表一些代码示例。

UPDATE

我按照James_D的build议设法做到了。 为了解决后台不得不等待控制台打印的问题,我实现了一个缓冲的控制台。 它存储要在StringBuffer打印的string,并在调用flush()方法时将其附加到TextArea 。 我使用AtomicBoolean来防止下一代发生,直到刷新完成,因为它是由一个Platform.runLater() runnable完成的。 另外请注意,这个解决scheme非常慢。

不知道我是否完全理解,但我认为这可能有帮助。

使用Platform.runLater(…)是一个适当的方法。

避免泛滥FX应用程序线程的技巧是使用Atomicvariables来存储您感兴趣的值。在Platform.runLater(…)方法中,检索它并将其设置为标记值。 在你的后台线程中,更新Atomicvariables,但是如果它已经被设置回它的sentinel值,只发布一个新的Platform.runLater(…)。

我通过查看Task的源代码来了解这一点。 看看updateMessage(..)方法(在编写本文时为1131行)是如何实现的。

这是一个使用相同技术的例子。 这只是一个(繁忙)后台线程计数尽可能快,更新一个IntegerProperty。 观察者观察该属性并用新值更新AtomicInteger。 如果AtomicInteger的当前值是-1,则调度一个Platform.runLater()。

在Platform.runLater中,我检索AtomicInteger的值并使用它来更新一个Label,并在该过程中将该值设置回-1。 这表示我已准备好进行其他UI更新。

 import java.text.NumberFormat; import java.util.concurrent.atomic.AtomicInteger; import javafx.application.Application; import javafx.application.Platform; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; public class ConcurrentModel extends Application { @Override public void start(Stage primaryStage) { final AtomicInteger count = new AtomicInteger(-1); final AnchorPane root = new AnchorPane(); final Label label = new Label(); final Model model = new Model(); final NumberFormat formatter = NumberFormat.getIntegerInstance(); formatter.setGroupingUsed(true); model.intProperty().addListener(new ChangeListener<Number>() { @Override public void changed(final ObservableValue<? extends Number> observable, final Number oldValue, final Number newValue) { if (count.getAndSet(newValue.intValue()) == -1) { Platform.runLater(new Runnable() { @Override public void run() { long value = count.getAndSet(-1); label.setText(formatter.format(value)); } }); } } }); final Button startButton = new Button("Start"); startButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { model.start(); } }); AnchorPane.setTopAnchor(label, 10.0); AnchorPane.setLeftAnchor(label, 10.0); AnchorPane.setBottomAnchor(startButton, 10.0); AnchorPane.setLeftAnchor(startButton, 10.0); root.getChildren().addAll(label, startButton); Scene scene = new Scene(root, 100, 100); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } public class Model extends Thread { private IntegerProperty intProperty; public Model() { intProperty = new SimpleIntegerProperty(this, "int", 0); setDaemon(true); } public int getInt() { return intProperty.get(); } public IntegerProperty intProperty() { return intProperty; } @Override public void run() { while (true) { intProperty.set(intProperty.get() + 1); } } } } 

如果你真的想从UI中“驱动”后端:那就是节制后端实现的速度,以便看到所有的更新,可以考虑使用一个AnimationTimer 。 一个AnimationTimer有一个handle(...) ,每帧渲染一次。 因此,您可以阻止后端实现(例如使用阻塞队列),并在每次调用handle方法时释放一次。 FX应用程序线程调用handle(...)方法。

handle(...)方法需要一个时间戳(纳秒)的参数,所以你可以使用它来进一步减慢更新,如果每帧一次太快的话。

例如:

 import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.beans.property.LongProperty; import javafx.beans.property.SimpleLongProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.TextArea; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; public class Main extends Application { @Override public void start(Stage primaryStage) { final BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(1); TextArea console = new TextArea(); Button startButton = new Button("Start"); startButton.setOnAction(event -> { MessageProducer producer = new MessageProducer(messageQueue); Thread t = new Thread(producer); t.setDaemon(true); t.start(); }); final LongProperty lastUpdate = new SimpleLongProperty(); final long minUpdateInterval = 0 ; // nanoseconds. Set to higher number to slow output. AnimationTimer timer = new AnimationTimer() { @Override public void handle(long now) { if (now - lastUpdate.get() > minUpdateInterval) { final String message = messageQueue.poll(); if (message != null) { console.appendText("\n" + message); } lastUpdate.set(now); } } }; timer.start(); HBox controls = new HBox(5, startButton); controls.setPadding(new Insets(10)); controls.setAlignment(Pos.CENTER); BorderPane root = new BorderPane(console, null, null, controls, null); Scene scene = new Scene(root,600,400); primaryStage.setScene(scene); primaryStage.show(); } private static class MessageProducer implements Runnable { private final BlockingQueue<String> messageQueue ; public MessageProducer(BlockingQueue<String> messageQueue) { this.messageQueue = messageQueue ; } @Override public void run() { long messageCount = 0 ; try { while (true) { final String message = "Message " + (++messageCount); messageQueue.put(message); } } catch (InterruptedException exc) { System.out.println("Message producer interrupted: exiting."); } } } public static void main(String[] args) { launch(args); } } 

执行此操作的最佳方法是使用JavaFx中的Task 。 这是迄今为止我所遇到的用于更新JavaFx中的UI控件的最好技术。

 Task task = new Task<Void>() { @Override public Void run() { static final int max = 1000000; for (int i=1; i<=max; i++) { updateProgress(i, max); } return null; } }; ProgressBar bar = new ProgressBar(); bar.progressProperty().bind(task.progressProperty()); new Thread(task).start();