Throttling javafx gui updates


Question

I receive data objects at random times at a high frequency, and need to update the JavaFX GUI with these. However, I don't want to fill the javafx event queue with a very large number of runnables (I use Platform.RunLater).

I have been thinking of how to best implement a throttling algorithm.

  • Would it be best to have a separate GUIUpdater thread that check for example a blocking queue for new objects, and then sleeps for example for 30ms and then checks again, in an infinite loop? In that case, would a blocking queue be the optimal data structure? Please note I only need the latest data object and the blockingQueue is a FIFO queue and I can't seem to pick only the latest entry.
  • Or - would it be better to simply just update the GUI with Platform.RunLater if nanoTime-startTime > 30ms? In that case, I don't need a separate thread to perform the Platform.RunLater-call. However - if an update is received when 30ms hasn't passed, and then no updates are received for some time, the last update will not show up in the GUI.

Any suggestions on how to design a throttling algorithm for JavaFX Platform.RunLater GUI updates in a short, efficient way?

1
15
5/6/2014 7:17:38 AM

Accepted Answer

This is the idiom used in the Task class for implementing the updateMessage(...) method, and other similar methods. It provides a nice, robust solution to avoid flooding the FX Application Thread:

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);
    }
}

The AtomicLong holds the current value to be used to update the Label. The count continually increments and updates the AtomicLong, but only schedules a call to Platform.runLater(...) if it's current value is -1. The Platform.runLater(...) updates the Label with the current value from the AtomicLong and flips the AtomicLong back to -1, indicating that it's ready for a new update.

The effect here is to schedule new calls to Platform.runLater(...) whenever the FX Application Thread is ready to handle them. There's no hard-coded time interval which could need tuning.

18
5/7/2014 1:30:37 AM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon