1

I am writing a JavaFX application and realized that too many things happen on FX thread. One of the root causes is that gui events, like button click, spawn backend actions on FX thread. Actually just after receiving the event, we're on FX thread, so any further calls stay on it. Is there any way to simply get back to MAIN application thread, and then Platform.runLater when is needed, or I have to deal with it using for instance RX or some executor services?

Thanks

Dreando
  • 579
  • 1
  • 4
  • 10
  • Read [this question and its answers](https://stackoverflow.com/questions/13784333/platform-runlater-and-task-in-javafx). – deHaar Sep 21 '18 at 07:56
  • If you look at [`Application.launch`](https://docs.oracle.com/javase/10/docs/api/javafx/application/Application.html#launch(java.lang.String...)), you'll see: "_The launch method does not return until the application has exited [...]_". The thread that calls `launch` simply creates the _JavaFX-Launcher_ thread and then waits on a `CountDownLatch`. The _JavaFX-Launcher_ thread initializes the toolkit, calls `Application.init`, and then hands control over to the _JavaFX Application Thread_; the launcher thread then waits as well. Given this, you normally can't use the main thread in JavaFX. – Slaw Sep 21 '18 at 11:57

1 Answers1

4

The way out of the Event Loop is very simple - it's just Java. Use anything you would use normally — executors, queues, etc.

For example, to get something done "in the background" and then update the GUI, you would do something like

final Executor backgroundWorker = Executors.newSingleThreadExecutor();
...
backgroundWorker.execute(()-> // from the EventLoop into the Worker
{
    val result = doThatLongRunningTask();
    Platform.runLater(() -> // back from the Worker to the Event Loop
    {
        updateUiWithResultOfLongRunningTask(result);
    }
});

I would normally expect the main thread to be given to the Event Loop and use a custom executor for background work (because the background work is application-specific, so may need more threads, etc.).


If for whatever exotic reason (there are really none I can think of) you want it the other way around:

So, to use the main thread as an executor all we need is:

public final class MyApp extends Application {
    private static final Logger LOG = LoggerFactory.getLogger(MyApp.class);
    private static final Runnable POISON_PILL = () -> {}; 
    private final BlockingQueue<Runnable> tasks = new LinkedBlockingQueue<>();
    private final Executor backgroundWorker = this::execute;
    private final Future<Void> shutdownFuture = new CompletableFuture<>();
    private final Executor eventLoop = Executors.newSingleThreadExecutor();

    /** Get background worker */
    public Executor getBackgroundWorker() {
        return backgroundWorker;
    } 

    /** Request backgroun worker shutdown */
    public Future shutdownBackgroundWorker() {
        execute(POISON_PILL);
        return shutdownFuture;
    }

    private void execute(Runnable task) {
        tasks.put(task);
    }

    private void runWorkerLoop() throws Throwable {
        Runnable task;
        while ((task = tasks.take()) != POISON_PILL) {
            task.run();
        }
        shutdownFuture.complete(null);
    }

    public static void main (String... args) throws Throwable {
        final MyApp myApp = new MyApp(args);        

        LOG.info("starting JavaFX (background) ...");
        eventLoop.execute(myApp::launch);

        LOG.info("scheduling a ping task into the background worker...");
        myApp.runLater(() -> {
            LOG.info("#1 we begin in the event loop");
            myApp.getBackgroundWorker().execute(() -> {
                LOG.info("#2 then jump over to the background worker");
                myApp.runLater(() -> {
                    LOG.info("#3 and then back to the event loop");
                });
            });
        });

        LOG.info("running the backgound worker (in the foreground)...");
        myApp.runWorkerLoop();
    }
}
bobah
  • 18,364
  • 2
  • 37
  • 70
  • Well, please do :) – Dreando Sep 21 '18 at 08:31
  • Does this work? I can't tell for certain, but it looks like you're calling `Application.launch(String...)` which, according to the documentation, blocks the calling thread until the JavaFX application exits. Also, `Application.launch` will create its _own_ instance of the `Application` subclass; note the method is `static` but being used in an instance context here. – Slaw Sep 21 '18 at 11:32
  • My guess from reading the Application class source code was that in launches the Event Loop thread asynchronously and not vandalizing the calling one. If what you say is correct that would be another reason why I would use main to host launch() and use a custom executor for background tasks (this way it will be much more conventional. – bobah Sep 21 '18 at 11:47
  • Looking at `Application.launch` it calls an internal class (`LauncherImpl`) which ultimately parks the calling thread in a `CountDownLatch` until the application exits. – Slaw Sep 21 '18 at 12:01
  • 1
    hmm ... both the question and this answer looks like kind-of upside-down thinking when it comes to ui: why do you want to "come back" to the main (aka: launcher) thread while the fx thread is running? Typically, you do all the short-timed work on the fx thread and when needed spawn another to do the heavy lifting that reports back into the fx thread ... – kleopatra Sep 21 '18 at 12:28
  • @kleopatra - the first suggestion in my answer is to use `main` as `Event Loop` and custom executors for background work. And no, spawning threads on demand is usually not a good idea. Much better is to have consistent executors /actors based concurrency model for background tasks. – bobah Sep 21 '18 at 12:32
  • okay, taking your word for "not a good idea" :) But then, why do spawn the application thread so weirdly (from - my - fx perspective)? If I really want/need access to a background backbone to care for all the heavy lifting, I would install it in init() and use those access methods you provided to just hand over the tasks I need to be done in the background (probably showing that I'm far from being a threading expert :) – kleopatra Sep 21 '18 at 12:39
  • 2
    @kleopatra - I did this upside-down example only to show how to do it. I also said I would not use it like that :-) (I've added a horisontal line separating practical and demonstrational parts). – bobah Sep 21 '18 at 12:45
  • didn't notice the line just jumped into the unusual part thanks! – kleopatra Sep 21 '18 at 12:48