Дросселирование javafx gui обновлений

Я получаю объекты данных в случайном порядке на высокой частоте, и им необходимо обновить GUI JavaFX. Тем не менее, я не хочу заполнять очередь событий javafx очень большим количеством runnables (я использую Platform.RunLater).

Я думал о том, как наилучшим образом реализовать алгоритм дросселирования.

  • Было бы лучше иметь отдельный поток GUIUpdater, который проверяет, например, блокирующую очередь для новых объектов, а затем спит, например, на 30 мс, а затем снова проверяет в бесконечном цикле? В таком случае будет ли блокирующая очередь оптимальной структурой данных? Обратите внимание: мне нужен только последний объект данных, а blockingQueue - очередь FIFO, и я не могу выбрать только последнюю запись.
  • Или - было бы лучше просто просто обновить GUI с помощью Platform.RunLater, если nanoTime-startTime > 30ms? В этом случае мне не нужен отдельный поток для выполнения вызова Platform.RunLater. Однако, если обновление получено, когда 30 мс не прошло, а затем никакие обновления не получены в течение некоторого времени, последнее обновление не будет отображаться в графическом интерфейсе.

Любые предложения по разработке короткого, эффективного способа разработки алгоритма дросселирования для платформы JavaFX Platform.RunLater GUI?

Ответы

Ответ 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, но только планирует вызов Platform.runLater(...), если текущее значение равно -1. Platform.runLater(...) обновляет Label с текущим значением из AtomicLong и переворачивает AtomicLong назад на -1, указывая, что он готов к новому обновлению.

Эффект здесь заключается в том, чтобы планировать новые вызовы Platform.runLater(...) всякий раз, когда поток прикладных программ FX готов к их обработке. Там нет жестко запрограммированного интервала времени, который может нуждаться в настройке.