【问题标题】:Throttling javafx gui updates限制 javafx gui 更新
【发布时间】:2014-06-22 16:30:36
【问题描述】:

我在随机时间以高频率接收数据对象,并且需要使用这些更新 JavaFX GUI。但是,我不想用大量可运行对象填充 javafx 事件队列(我使用 Platform.RunLater)。

我一直在思考如何最好地实现节流算法。

  • 最好有一个单独的 GUIUpdater 线程来检查新对象的阻塞队列,然后休眠 30 毫秒,然后在无限循环中再次检查?在那种情况下,阻塞队列会是最优的数据结构吗?请注意,我只需要最新的数据对象,blockingQueue 是一个 FIFO 队列,我似乎不能只选择最新的条目。
  • 或者 - 如果 nanoTime-startTime > 30 毫秒,只需使用 Platform.RunLater 更新 GUI 会更好吗?在这种情况下,我不需要单独的线程来执行 Platform.RunLater 调用。但是 - 如果在 30 毫秒内收到更新,然后一段时间内没有收到更新,则最后一次更新不会显示在 GUI 中。

关于如何为 JavaFX Platform.RunLater GUI 更新设计节流算法有什么建议吗?

【问题讨论】:

    标签: java multithreading user-interface thread-safety javafx


    【解决方案1】:

    这是Task 类中用于实现updateMessage(...) 方法和其他类似方法的惯用语。它提供了一个很好的、强大的解决方案来避免 FX 应用程序线程泛滥:

    import java.util.concurrent.atomic.AtomicLong;
    
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    public class ThrottlingCounter extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            final AtomicLong counter = new AtomicLong(-1);
            final Label label = new Label();
            final Thread countThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    long count = 0 ;
                    while (true) {
                        count++ ;
                        if (counter.getAndSet(count) == -1) {
                            updateUI(counter, label);
                        }
                    }
                }
            });
            countThread.setDaemon(true);
            countThread.start();
    
            VBox root = new VBox();
            root.getChildren().add(label);
            root.setPadding(new Insets(5));
            root.setAlignment(Pos.CENTER);
    
            Scene scene = new Scene(root, 150, 100);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        private void updateUI(final AtomicLong counter,
                final Label label) {
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    final String msg = String.format("Count: %,d", counter.getAndSet(-1));
                    label.setText(msg);
                }
            });
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    AtomicLong 保存用于更新标签的当前值。计数不断增加并更新AtomicLong,但仅在当前值为-1 时安排对Platform.runLater(...) 的调用。 Platform.runLater(...) 使用 AtomicLong 的当前值更新 Label 并将 AtomicLong 翻转回 -1,表示它已准备好进行新的更新。

    这里的效果是在 FX 应用程序线程准备好处理它们时安排对Platform.runLater(...) 的新调用。没有可能需要调整的硬编码时间间隔。

    【讨论】:

    • 为什么在 platform.runlater-runnable 中设置标签后,AtomicLong 没有设置为“-1”(表示 gui 已准备好更新)?
    • @user3607022 您需要检索当前值并将该值设置为-1原子地。如果您检索了要显示的值,然后在显示后将其设置为-1,则其他线程可能会在这两者之间再次设置该值:即1. UI线程检索该值,2.后台线程更新自值为!=-1 没有计划更新,3. ui 将值设置为-1。在这种情况下,您可能会“错过”该中间值;如果这是线程做的最后一件事,你会永远错过它。
    • 有没有一种简单的方法可以准确地描述哪些更新被丢弃了?
    • @user3607022 不:没有真正的理由关心。您正在专门更新 UI 属性;如果 UI 没有时间显示它,那么如果它没有收到也没关系:最终用户不会注意到。
    • 我明白了。因此,换句话说,被删除的更新是 UI 没有时间显示的更新。我想我必须考虑一下这个实现是如何产生这个结果的。非常感谢您的帮助。
    猜你喜欢
    • 2014-12-20
    • 2016-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-03
    • 2014-12-12
    • 2013-12-04
    相关资源
    最近更新 更多