Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX periodic background task

Tags:

javafx

I try to run in JavaFX application background thread periodically, which modifies some GUI property.

I think I know how to use Task and Service classes from javafx.concurrent and can't figure it out how to run such periodic task without using Thread#sleep() method. It would be nice if I can use some Executor from Executors fabricate methods (Executors.newSingleThreadScheduledExecutor())

I tried to run Runnable every 5 sec, which restarts javafx.concurrent.Service but it hangs immediately as service.restart or even service.getState() is called.

So finally I use Executors.newSingleThreadScheduledExecutor(), which fires my Runnable every 5 sec and that Runnable runs another Runnable using:

Platform.runLater(new Runnable() {  //here i can modify GUI properties } 

It looks very nasty :( Is there a better way to do this using Task or Service classes?

like image 959
Michał Króliczek Avatar asked Apr 01 '12 16:04

Michał Króliczek


People also ask

Is JavaFX multithreaded?

JavaFX provides a complete package to deal with the issues of multithreading and concurrency. There is an interface called Worker, an abstract class called Task, and ScheduledService for this purpose. The Task is basically a Worker implementation, ideal for implementing long running computation.

How do I create a countdown timer in JavaFX?

To start the timer, you click the Start Timer button. The numeric display then counts down—once per second—to zero. Anytime you click the Start Timer button, the timer resets to 15 and restarts the countdown. Listing 1 shows all of the code except the button's event handler code (lines 46-51), which we'll see later.

What are three life cycle methods of JavaFX application?

Lifecycle of JavaFX Applicationstart() − The entry point method where the JavaFX graphics code is to be written. stop() − An empty method which can be overridden, here you can write the logic to stop the application. init() − An empty method which can be overridden, but you cannot create stage or scene in this method.

How do I add a delay in JavaFX?

The easiest way to delay a java program is by using Thread. sleep() method. The sleep() method is present in the Thread class. It simply pauses the current thread to sleep for a specific time.


2 Answers

You can use Timeline for that task:

Timeline fiveSecondsWonder = new Timeline(                  new KeyFrame(Duration.seconds(5),                   new EventHandler<ActionEvent>() {      @Override     public void handle(ActionEvent event) {         System.out.println("this is called every 5 seconds on UI thread");     } })); fiveSecondsWonder.setCycleCount(Timeline.INDEFINITE); fiveSecondsWonder.play(); 

for the background processes (which don't do anything to the UI) you can use old good java.util.Timer:

new Timer().schedule(     new TimerTask() {          @Override         public void run() {             System.out.println("ping");         }     }, 0, 5000); 
like image 101
Sergey Grinev Avatar answered Sep 27 '22 21:09

Sergey Grinev


Preface: This question is often the duplicate target for questions which ask how to perform periodic actions in JavaFX, whether the action should be done in the background or not. While there are already great answers to this question, this answer attempts to consolidate all the given information (and more) into a single answer and explain/show the differences between each approach.

This answer focuses on the APIs available in JavaSE and JavaFX and not third-party libraries such as ReactFX (showcased in Tomas Mikula's answer).


Background Information: JavaFX & Threads

Like most mainstream GUI frameworks, JavaFX is single-threaded. This means there's a single thread dedicated to reading and writing the state of the UI and processing user-generated events (e.g. mouse events, key events, etc.). In JavaFX this thread is called the "JavaFX Application Thread", sometimes shortened to just "FX thread", but other frameworks may call it something else. Some other names include "UI thread", "event-dispatch thread", and "main thread".

It is absolutely paramount that anything connected to the GUI showing on screen is only ever accessed or manipulated on the JavaFX Application Thread. The JavaFX framework is not thread-safe and using a different thread to improperly read or write the state of the UI can lead to undefined behavior. Even if you don't see any externally-visible problems, access to state shared between threads without the necessary synchronization is broken code.

Many GUI objects, however, can be manipulated on any thread as long as they aren't "live". From the documentation of javafx.scene.Node:

Node objects may be constructed and modified on any thread as long they are not yet attached to a Scene in a Window that is showing [emphasis added]. An application must attach nodes to such a Scene or modify them on the JavaFX Application Thread.

But other GUI objects, such as Window and even some subclasses of Node (e.g. WebView), are more strict. For instance, from the documentation of javafx.stage.Window:

Window objects must be constructed and modified on the JavaFX Application Thread.

If you're unsure about the threading rules of a GUI object, its documentation should provide the needed information.

Since JavaFX is single-threaded you also have to make sure never to block or otherwise monopolize the FX thread. If the thread is not free to do its job then the UI is never redrawn and new user-generated events can't be processed. Not following this rule can lead to the infamous unresponsive/frozen UI and your users are not happy.

It's virtually always wrong to sleep the JavaFX Application Thread.


Periodic Tasks

There are two different kinds of periodic tasks, at least for the purposes of this answer:

  1. Periodic foreground "tasks".
    • This could include things such as a "blinking" node or periodically switching between images.
  2. Periodic background tasks.
    • An example might be periodically checking a remote server for updates and, if there are any, downloading the new information and displaying it to the user.

Periodic Foreground Tasks

If your periodic task is short and simple then using a background thread is overkill and just adds unnecessary complexity. The more appropriate solution is to use the javafx.animation API. Animations are asynchronous but stay entirely within the JavaFX Application Thread. In other words, animations provide a way to "loop" on the FX thread, with delays between each iteration, without actually using loops.

There are three classes uniquely suited to periodic foreground tasks.

Timeline

A Timeline is made up of one or more KeyFrames. Each KeyFrame has a specified time of when it should complete. Each one can also have an "on finished" handler which is invoked after the specified amount of time has elapsed. This means you can create a Timeline with a single KeyFrame that periodically executes an action, looping as many times as you want (including forever).

import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.StackPane; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import javafx.util.Duration;  public class App extends Application {    @Override   public void start(Stage primaryStage) {     Rectangle rect = new Rectangle(100, 100);      // toggle the visibility of 'rect' every 500ms     Timeline timeline =         new Timeline(new KeyFrame(Duration.millis(500), e -> rect.setVisible(!rect.isVisible())));     timeline.setCycleCount(Animation.INDEFINITE); // loop forever     timeline.play();      primaryStage.setScene(new Scene(new StackPane(rect), 200, 200));     primaryStage.show();   } } 

Since a Timeline can have more than one KeyFrame it's possible to have actions being executed at different intervals. Just keep in mind that the times of each KeyFrame do not stack. If you have one KeyFrame with a time of two seconds followed by another KeyFrame with a time of two seconds, both KeyFrames will finish two seconds after the animation is started. To have the second KeyFrame finish two seconds after the first one, its time needs to be four seconds.

PauseTransition

Unlike the other animation classes, a PauseTransition is not used to actually animate anything. It's main purpose is to be used as a child of SequentialTransition to put a pause between two other animations. However, like all subclassses of Animation it can have an "on finished" handler that's executed after it completes, allowing it to be used for periodic tasks.

import javafx.animation.PauseTransition; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.StackPane; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import javafx.util.Duration;  public class App extends Application {    @Override   public void start(Stage primaryStage) {     Rectangle rect = new Rectangle(100, 100);      // toggle the visibility of 'rect' every 500ms     PauseTransition pause = new PauseTransition(Duration.millis(500));     pause.setOnFinished(         e -> {           rect.setVisible(!rect.isVisible());           pause.playFromStart(); // loop again         });     pause.play();      primaryStage.setScene(new Scene(new StackPane(rect), 200, 200));     primaryStage.show();   } } 

Notice the on-finished handler invokes playFromStart(). This is necessary to "loop" the animation again. The cycleCount property can't be used since the on-finished handler is not invoked at the end of each cycle, it's only invoked at the end of the last cycle. The same thing is true of Timeline; the reason it works with Timeline above is because the on-finished handler isn't registered with the Timeline but with the KeyFrame.

Since the cycleCount property can't be used for PauseTransition for multiple cycles it makes it more difficult to loop only a certain number of times (rather than forever). You have to keep track of the state yourself and only invoke playFromStart() when appropriate. Keep in mind that local variables declared outside a lambda expression or anonymous class but used inside said lambda expression or anonymous class must be final or effectively final.

AnimationTimer

The AnimationTimer class is the lowest level of JavaFX's animation API. It's not a subclass of Animation and thus doesn't have any of the properties that were used above. Instead, it has an abstract method that, when the timer is started, is invoked once per frame with the timestamp (in nanoseconds) of the current frame: #handle(long). In order to execute something periodically with AnimationTimer (other than once per frame) will require manually calculating the time differences between invocations of handle using the method's argument.

import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.StackPane; import javafx.scene.shape.Rectangle; import javafx.stage.Stage;  public class App extends Application {    @Override   public void start(Stage primaryStage) {     Rectangle rect = new Rectangle(100, 100);      // toggle the visibility of 'rect' every 500ms     AnimationTimer timer =         new AnimationTimer() {            private long lastToggle;            @Override           public void handle(long now) {             if (lastToggle == 0L) {               lastToggle = now;             } else {               long diff = now - lastToggle;               if (diff >= 500_000_000L) { // 500,000,000ns == 500ms                 rect.setVisible(!rect.isVisible());                 lastToggle = now;               }             }           }         };     timer.start();      primaryStage.setScene(new Scene(new StackPane(rect), 200, 200));     primaryStage.show();   } } 

For most use cases similar to the above, using either Timeline or PauseTransition would be the better option.

Periodic Background Tasks

If your periodic task is time-consuming (e.g. expensive computations) or blocking (e.g. I/O) then a background thread needs to be used. JavaFX comes with some concurrency utilities built-in to aid with communication between background threads and the FX thread. These utilities are described in:

  • The Concurrency in JavaFX tutorial, and
  • The documentation of the classes in the javafx.concurrent package.

For periodic background tasks that need to communicate with the FX thread, the class to use is javafx.concurrent.ScheduledService. That class will execute its task periodically, restarting after successful execution, based on a specified period. If configured to do so it will even retry a configurable amount of times after failed executions.

import javafx.application.Application; import javafx.beans.binding.Bindings; import javafx.concurrent.ScheduledService; import javafx.concurrent.Task; import javafx.concurrent.Worker.State; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.control.ProgressBar; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.stage.Stage; import javafx.util.Duration;  public class App extends Application {    // maintain a strong reference to the service   private UpdateCheckService service;    @Override   public void start(Stage primaryStage) {     service = new UpdateCheckService();     service.setPeriod(Duration.seconds(5));      Label resultLabel = new Label();     service.setOnRunning(e -> resultLabel.setText(null));     service.setOnSucceeded(         e -> {           if (service.getValue()) {             resultLabel.setText("UPDATES AVAILABLE");           } else {             resultLabel.setText("UP-TO-DATE");           }         });      Label msgLabel = new Label();     msgLabel.textProperty().bind(service.messageProperty());      ProgressBar progBar = new ProgressBar();     progBar.setMaxWidth(Double.MAX_VALUE);     progBar.progressProperty().bind(service.progressProperty());     progBar.visibleProperty().bind(service.stateProperty().isEqualTo(State.RUNNING));      VBox box = new VBox(3, msgLabel, progBar);     box.setMaxHeight(Region.USE_PREF_SIZE);     box.setPadding(new Insets(3));      StackPane root = new StackPane(resultLabel, box);     StackPane.setAlignment(box, Pos.BOTTOM_LEFT);      primaryStage.setScene(new Scene(root, 400, 200));     primaryStage.show();      service.start();   }    private static class UpdateCheckService extends ScheduledService<Boolean> {      @Override     protected Task<Boolean> createTask() {       return new Task<>() {          @Override         protected Boolean call() throws Exception {           updateMessage("Checking for updates...");           for (int i = 0; i < 1000; i++) {             updateProgress(i + 1, 1000);             Thread.sleep(1L); // fake time-consuming work           }           return Math.random() < 0.5; // 50-50 chance updates are "available"         }       };     }   } } 

Here's a note from the documentation of ScheduledService:

Timing for this class is not absolutely reliable. A very busy event thread might introduce some timing lag into the beginning of the execution of the background Task, so very small values for the period or delay are likely to be inaccurate. A delay or period in the hundreds of milliseconds or larger should be fairly reliable.

And another:

The ScheduledService introduces a new property called lastValue. The lastValue is the value that was last successfully computed. Because a Service clears its value property on each run, and because the ScheduledService will reschedule a run immediately after completion (unless it enters the cancelled or failed states), the value property is not overly useful on a ScheduledService. In most cases you will want to instead use the value returned by lastValue.

The last note means binding to the value property of a ScheduledService is in all likelihood useless. The example above works despite querying the value property because the property is queried in the onSucceeded handler, before the service is rescheduled.

No Interaction with UI

If the periodic background task does not need to interact with the UI then you can use the standard APIs of Java instead. More specifically, either:

  • The java.util.Timer class (not javax.swing.Timer),
  • Or the more modern java.util.concurrent.ScheduledExecutorService interface.

Note that ScheduledExecutorService supports thread pools, unlike Timer which only supports a single thread.

ScheduledService is not an Option

If for whatever reason you can't use ScheduledService, but need to need to interact with the UI anyway, then you need to make sure the code interacting with the UI, and only that code, is executed on the FX thread. This can be accomplished by using Platform#runLater(Runnable).

Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted. A runnable passed into the runLater method will be executed before any Runnable passed into a subsequent call to runLater. If this method is called after the JavaFX runtime has been shutdown, the call will be ignored: the Runnable will not be executed and no exception will be thrown.

NOTE: applications should avoid flooding JavaFX with too many pending Runnables. Otherwise, the application may become unresponsive. Applications are encouraged to batch up multiple operations into fewer runLater calls. Additionally, long-running operations should be done on a background thread where possible, freeing up the JavaFX Application Thread for GUI operations.

[...]

Heed the note from the above documentation. The javafx.concurent.Task class avoids this by coalescing updates to its message, progress, and value properties. This is currently implemented by using an AtomicReference and strategic get-and-set operations. If interested, you can take a look at the implementation (JavaFX is open source).

like image 29
Slaw Avatar answered Sep 27 '22 23:09

Slaw