Simulating Platform.runAndWait

Every JavaFX application maintains one single JavaFX application thread running the application’s event queue, much like Swing’s event dispatch thread. System-generated events such as mouse clicks are automatically inserted into the event queue. JavaFX also provides the method Platform.runLater to programmatically enqueue an arbitrary Runnable function object.

You might occasionally wish to call runLater from the JavaFX application thread itself, to ensure some action happens only after the currently executing method has returned to the event dispatcher. However, the more important purpose of runLater is to allow UI access from other threads. Generally, JavaFX UI methods must be called from the application thread or you’ll get an exception. (This is how most GUI systems work – Graham Hamilton explains why.) But the Platform methods are safe to call from any thread, so background worker threads can use runLater to manipulate the UI.

There is one problem with this mechanism. True to its name, runLater always immediately returns to the caller. The specified action is executed at some unknown later time, namely when the event dispatcher gets around to handling the corresponding event. On the JavaFX application thread you can simply execute the action directly if you need to wait for its completion, but from a background thread that’s not an option. Swing provided an invokeAndWait to go along with invokeLater, but JavaFX 8 still offers no Platform.runAndWait. What to do?

Wait for runLater

The OpenJFX source code actually does define a method called Platform.runAndWait. This method executes the specified action directly when called from the JavaFX application thread, and guards runLater with a CountDownLatch to block until completion when called from another thread. However, public access is currently commented out, possibly because there’s no decent solution for error handling. The OpenJFX implementation simply prints stack traces, while my own implementation below just ignores exceptions. You can find the private OpenJFX implementation in class PlatformImpl:

modules/graphics/src/main/java/com/sun/javafx/application/PlatformImpl.java

My simplified version below skips various checks in the OpenJFX implementation. This is not a solution fit for a general library but it’s a convenient hack for situations when bad things are unlikely to happen. In particular, I don’t except JavaFX to suddenly shut down (which might deadlock the await call) nor the specified action to throw any interesting exceptions. I also ignore any InterruptedException while waiting since I assume the application would exit anyway.

/**
 * Runs the specified {@link Runnable} on the
 * JavaFX application thread and waits for completion.
 *
 * @param action the {@link Runnable} to run
 * @throws NullPointerException if {@code action} is {@code null}
 */
public static void runAndWait(Runnable action) {
    if (action == null)
        throw new NullPointerException("action");

    // run synchronously on JavaFX thread
    if (Platform.isFxApplicationThread()) {
        action.run();
        return;
    }

    // queue on JavaFX thread and wait for completion
    final CountDownLatch doneLatch = new CountDownLatch(1);
    Platform.runLater(() -> {
        try {
            action.run();
        } finally {
            doneLatch.countDown();
        }
    });

    try {
        doneLatch.await();
    } catch (InterruptedException e) {
        // ignore exception
    }
}

Semantic Issues

There are two potentially problematic issues that arise from the nature of runLater and the event queue mechanism. First, runLater swallows any exceptions that the executed action might throw, as it’s not designed to communicate anything back to the caller. If you want error handling you need to specialize runAndWait for a Runnable subclass that provides such communication, e.g. FutureTask, or else implement your own custom scheme.

The second issue is more subtle. Although runAndWait waits for completion of the runLater call, that call still inserts the specified action at the end of the event queue. Any events that are already present will be processed first, and only then will the new action begin executing. However, when calling runAndWait from the JavaFX application thread the action is executed immediately, regardless of any present event queue contents.

Could you simply remove the entire isFxApplicationThread branch to do a blocking wait from the JavaFX application thread? No, you couldn’t. The blocking wait would block the application thread itself, preventing any further event queue processing and deadlocking the application. When calling from the JavaFX application thread, you must decide whether to run the action directly and implicitly wait for completion, or else enqueue with runLater and forego waiting for completion. Only background threads can both enqueue and wait.

14 thoughts on “Simulating Platform.runAndWait

  1. Anonymous

    Another possibility is to instantiate a java..util.concurrent.FutureTask with a Callable as argument. The FutureTask itself implements runnable, so you can pass it to Platform.runLater. If you call the get method on the FutureTask, the current thread wait’s until your task is done. And you get some kind of Exception handling for free, like on all implementations of java.util.concurrent.Future…

    Reply
    1. Christoph Nahr Post author

      Yes, that was indeed my own implementation before I looked at the OpenJFX source code. I decided to adopt its use of CountDownLatch since it’s probably the most minimal solution when you don’t need exception handling. Otherwise, wrapping the supplied action in a FutureTask is certainly a viable option.

      Reply
  2. Pingback: JavaFX links of the week, May 5 // JavaFX News, Demos and Insight // FX Experience

    1. Christoph Nahr Post author

      Don’t use that version! It doesn’t check for calls from the JavaFX application thread which means you can easily get the application deadlock described in the last paragraph of my post.

      Reply
        1. Christoph Nahr Post author

          Yeah, good luck finding such a bug. The Javadoc comments don’t even mention that you must never call the JFXtras version from the JavaFX application thread, so how are clients supposed to know that? Checking for Platform.isFxApplicationThread() is the right thing to do.

  3. tbeernot

    So the question would be; should an exception be thrown for calling runAndWait inside the FAT, or make it go away and not tell the caller about his mistake? The expectation of the call is that after it returns the runnable has finished executing, and in your implementation this is the case, so I agree.

    Reply
    1. Christoph Nahr Post author

      Throwing an exception on the application thread would be a good choice for JFXtras, I think. It would match the current behavior which effectively requires calling from a different thread anyway, and it’s clearer than my implementation since there’s no subtle distinction of queuing vs immediate execution.

      Reply
  4. Sebastien Bordes

    JRebirth Application Framework handle this need by using sequential MultiCommand.
    A DefaultUICommand (will do the graphical update) and then a DefaultCommand will perform a long task into a thread pool (or whatever you need like another ui change or internal JRebirth change).

    JRebirth Application frameworks provides 4 kinds of threads:
    – JAT ==> JavaFX Application Thread
    – JIT ==> JRebirth Internal Thread (perform JRebirth wave registration and other custom stuff)
    – JTP ==> First Thread Pool (perform long task)
    – HPTP ==> Second Thread Pool to perform higher priority task

    @Sequential
    public class MyMultiCommand extends DefaultMultiCommand {

    @Override
    protected void manageSubCommand() {
    addCommandClass(UiCommand.class);
    addCommandClass(LongCommand.class);
    }
    }

    public class UiCommand extends DefaultUICommand {

    @Override
    protected void perform(Wave wave) {

    Stage s = new Stage();
    s.show();
    s.close();
    System.out.println(“ui command done”);
    }

    }

    public class LongCommand extends DefaultCommand {

    @Override
    protected void perform(Wave wave) throws CommandException {
    try {
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(“long command done”);
    }

    }

    And call them into the preBoot phase (in example) of your JRebirth Application Class like this.

    @Override
    public List getPreBootWaveList() {
    return Collections.singletonList(WaveBase.callCommand(MyMultiCommand.class));
    }

    The long task will be performed after the end of the UI task

    Reply
  5. Pingback: Tip #2: How to runAndWait with JRebirth Thread Engine | JRebirth's Blog

  6. pvg8v6g

    I know this is a HUGE necro but seriously I’m so thankful for this little snippet. Totally solved the issue I was having. Calling a FutureTask inside a Task was running the FutureTask but not pausing the Task execution for anything like Alert popups. This totally fixed the problem thank you!

    Reply

Leave a Reply