inyourcorner
inyourcorner

Reputation: 693

When does the JavaFX Thread paint to the display?

I've noticed a Platform.runLater() doesn't update the stage/screen immediately after running, so I'm guessing the painting is happening elsewhere or on another queued event. I'm curious as to when or how the actual painting or rendering to the screen is queued or signaled, after the runnable completes.

The following code will print 1.begin, 1.end, 2.begin, 2.end, 3.begin, 3.end to the console, but the label never shows 1, though the second runLater() waits.

package main;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.util.concurrent.CountDownLatch;

public class SmallRunLater extends Application {

    SimpleStringProperty labelValue = new SimpleStringProperty("0");

    @Override
    public void start(Stage stage) throws InterruptedException {

        Scene scene = new Scene(new Group());
        stage.setWidth(550);
        stage.setHeight(550);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));

        Label label = new Label();
        label.textProperty().bind(labelValue);
        Button startBtn = new Button("Start");
        vbox.getChildren().addAll(startBtn, label);

        startBtn.setOnAction((action) -> {
            try {
                Task task = new Task<Void>() {
                    @Override
                    protected Void call() throws Exception {
                        SmallWork work = new SmallWork();
                        work.doWork(labelValue);
                        return null;
                    }
                };
                new Thread(task).start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        ((Group) scene.getRoot()).getChildren().addAll(vbox);

        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}

class SmallWork {

    public void doWork(SimpleStringProperty labelValue) throws InterruptedException {

        Platform.runLater(() -> {
            System.out.println("1.begin");
            labelValue.set("1");
            System.out.println("1.end");
        });

        runNow(() -> {
            System.out.println("2.begin");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            labelValue.set("2");
            System.out.println("2.end");
        });

        Platform.runLater(() -> {
            System.out.println("3.begin");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            labelValue.set("3");
            System.out.println("3.end");

        });
    }

    public static void runNow(Runnable r){

        final CountDownLatch doneLatch = new CountDownLatch(1);

        Platform.runLater(() -> {
            try {
                r.run();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                doneLatch.countDown();
            }
        });

        try {
            doneLatch.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Upvotes: 2

Views: 498

Answers (1)

AlmasB
AlmasB

Reputation: 3407

Yes, you are right, Platform.runLater() (as implied by the name) doesn't run right away and just pushes the Runnable to the internal queue. There is an internal render tick deep down. The Runnable objects will be executed in the update tick just before render. The fact that label never shows "1" simply coincides with the fact that your runNow() gets called immediately, in the same tick, so the two Runnable objects get pushed to same queue and executed in the same tick within JavaFX internal loop. Hence, the following happens:

  1. label set to 1
  2. internal thread set to sleep. This actually freezes the application if you noticed, since the rendering thread is now sleeping
  3. label set to 2
  4. render tick happens, so we get to see "2"
  5. ...

I have tried running the code above and sometimes I can see 1, which means the two Runnable objects were pushed in different ticks. Something like that:

  1. label set to 1
  2. render tick
  3. ...

Upvotes: 3

Related Questions