Reputation: 7526
There seems to be a common understanding that the JavaFX UI thread is caped at 60 updates per second(1, 2). It is my understanding that update means pulse
.
A pulse is an event that indicates to the JavaFX scene graph that it is time to synchronize the state of the elements on the scene graph with Prism. A pulse is throttled at 60 frames per second (fps) maximum and is fired whenever animations are running on the scene graph. Even when animation is not running, a pulse is scheduled when something in the scene graph is changed. For example, if a position of a button is changed, a pulse is scheduled.
Source: https://docs.oracle.com/javafx/2/architecture/jfxpub-architecture.htm
To figure out what happens if there are for example more than 60 calls to Platform.runLater
I wrote this small program:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class HighUITraffic extends Application {
private int counter = 0;
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
long sheduleTime = System.currentTimeMillis();
System.out.println("Task "+counter+" run at "+sdf.format(new Date(sheduleTime)));
Platform.runLater(new RunInUI(counter));
counter++;
}
};
timer.schedule(task, 0, 10); // every 10ms => 100 per second
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
private static class RunInUI implements Runnable {
private final int counter;
public RunInUI(int counter) {
this.counter = counter;
}
@Override
public void run() {
long executionTime = System.currentTimeMillis();
System.out.println("Task "+counter+" executed in Application Thread at "+sdf.format(new Date(executionTime)));
}
}
}
My expectation were that either:
Runnable
s are stacked out and the UI thread runs interspersed and then executes all Runables
that have queued up.Runable
s up.However what happened after some initial delay where the Runables queued up and where then all handled in one go by the UI thread, was that the two threads run in order:
Task 1281 run at 2016-01-25T18:37:00.269 Task 1281 executed in Application Thread at 2016-01-25T18:37:00.269 Task 1282 run at 2016-01-25T18:37:00.274 Task 1282 executed in Application Thread at 2016-01-25T18:37:00.274 Task 1283 run at 2016-01-25T18:37:00.279
And there were more than 60 calls of either thread during one second (I tried with 100 and 200, without any difference).
Therefore I am confused:
What I wanted to know in the first place is what does happen with Runable
s that are pushed on the UI thread if the thread has reached its cap limit. Are all Runable
s that have queued up executed in sequence in one run of the UI thread or is only one Runable
executed and the rest has to wait? The second case would cause serious problems, when continuously pushing more Runable
s on the UI thread than that limit.
Upvotes: 2
Views: 2846
Reputation: 209724
Pulses are capped at 60fps. Runnable
s submitted to the FX Application Thread are not.
As I understand it, there are two threads: the FX Application Thread, and the prism rendering thread. The FX Application Thread consumes Runnable
s from a queue and executes them. The prism rendering thread executes up to 60 times per second, synchronizing with the FX Application Thread (thereby temporarily preventing it from executing new runnables) and rendering the frame, then releasing the FX Application Thread.
Thus the FX Application Thread will execute your Runnable
as soon as it can, and is not limited to one runnable every 1/60s; but it will not take a new runnable from the queue during a frame render. (User events are processed on the FX Application Thread in a similar manner to the Runnable
s you post.)
So there are a couple of ways to make bad things happen. One is to have code on the FX Application Thread (whether in an event handler or in a Runnable
posted to Platform.runLater()
) that takes a long time to execute. Doing this will block the prism rendering thread from synchronizing with the FX Application thread, meaning that the next frame cannot be rendered until the runnable is complete. Another is to bombard the FX Application Thread with too many Runnable
s, so that the queue is growing faster than they can be consumed. (Essentially, since the queue always has something available, the FX Application Thread becomes a busy thread in these circumstances, and there is no guarantee the prism rendering thread ever gets to run.)
So the direct answer to your question is that the Runnable
s are executed in the order they are posted, as promptly as they can be on a single thread. A frame rendering, which is capped at 60 fps, will temporarily stop consumption of the Runnable
s from the queue.
In pseudocode, I guess (just my intuition as to how this works, this is not based on the source code) the FX Application Thread looks something like
while (fxToolkitRunning()) {
Runnable runnable = runnableQueue.take(); // block until something available
acquireLock();
runnable.run();
releaseLock();
}
and the prism rendering thread looks something like:
while (fxToolkitRunning()) {
while (! timeForNextFrame()) {
sleep(timeUntilNextFrame());
}
acquireFXApplicationThreadLock();
if (sceneNeedsUpdating()) {
renderFrame();
}
releaseFXApplicationThreadLock();
}
Upvotes: 5