Concurrency in JavaFX

Matthew Glover
5 min readAug 1, 2017

--

I am working on a GUI for a Tic Tac Toe app, using JavaFX. Having spent most of my time programming in the single-threaded world of JavaScript, I’m not too familiar with the concept of multiple threads and concurrency. And I didn’t envisage needing to get too involved with threads for my simple Tic Tac Toe app. The catch came when I tried to deal with the computer vs computer game.

App structure

My first naive solution had a Model class which initialises two ComputerPlayer instances (for X and O). Each ComputerPlayer is called in turn with a request for its move. The ComputerPlayer then uses the current Game to decide on its next move. Once it had selected its move, the Model makes the move on the current Game instance, and then notifies its Observers (the UI instances) that a GAME_MOVE event has occurred. On receipt of a GAME_MOVE event, the BoardUI re-renders the board. The Model then requests the other ComputerPlayer’s move, and the process repeats until the game is over.

Adding delay between moves: the problems start

All of this worked, but the speed of the computer meant that you could not see each move being made sequentially, so I added some delay using Thread.sleep(delayInMilliseconds), to slow down the process. This pretty much mirrored the approach I’d used successfully with the terminal UI.

My initial, simple code, was as follows:

...
try {
Thread.sleep(delayInMilliseconds); // Delay thread
} catch (Exception e) {
// Do nothing with errors
}
int move = nextPlayer.getMove(game); // Get computer player's move
runMove(move); // Makes move to game and notify UI
...

However, when I played the game, what I got was a very long pause (I was using a 1 second delay between moves), and then eventually the final state of the board was rendered, with none of the interim steps showing. This was not the UX I was hoping for!

Understanding the problem

In order to understand what was happening, I printed the board state after each move (here I was able to use my BoardFormatter class from the console ui to helpfully visualise what was happening). From the output, I could see that the board states were indeed being generated at roughly 1 second intervals, and the BoardUI was doing nothing until after the final move had been made. Now I knew that the sleep and move code was working as expected, I needed to figure out why the UI was not updating as expected.

It seemed likely that the problem had something to do with the Thread.sleep method and JavaFX (I had no problem with human vs human games), so a google of “Thread.sleep JavaFX”, seemed like a good starting point. Indeed it through up this answer on Stack Overflow, which pointed to the JavaFX docs page on Concurrency in JavaFX, which states:

The JavaFX scene graph, which represents the graphical user interface of a JavaFX application, is not thread-safe and can only be accessed and modified from the UI thread also known as the JavaFX Application thread. Implementing long-running tasks on the JavaFX Application thread inevitably makes an application UI unresponsive. A best practice is to do these tasks on one or more background threads and let the JavaFX Application thread process user events.

From this it’s clear that my Thread.sleep call was blocking the UI thread, which means the UI cannot update.

A first solution

My first solution was to move the sleep to its own thread using the javafx.concurrent.Task class, as described in the JavaFX docs (linked to above):

...
// Create Task instance
Task<Void> task = new Task<Void>() {
// Implement required call() method
@Override
protected Void call() throws Exception {
// Add delay code from initial attempt
try {
Thread.sleep(delayInMilliseconds);
} catch (Exception e) {
}

runMove(nextPlayer.getMove(game));

// We're not interested in the return value, so return null
return null;
}
};
// Run task in new thread
new Thread(task).start();
...

Whilst there is quite a lot of new code, most is boilerplate. All that’s really happening is that the original code is now wrapped in a Task instance, which is then run in a new Thread.

The next problem: scheduling a task onto the JavaFX thread

Unfortunately this did not work. Indeed, if anything things got worse. Instead of the BoardUI only updating after the game had finished, after the update, the BoardUI did not update at all, so all that could be seen was an empty board.

Using the print out, I could see that all moves were still being made, so the task and new thread code was making the moves as before, but these were now not being reflected in the UI at all.

With a bit more googling, I came to understand what was going on: The Task and new Thread code solved the problem of blocking the JavaFX thread, by running the sleep code on a separate thread. However, the runMove(nextPlayer.getMove(game)); line is will cause a UI update (via an Observable/Observer interface between the Model and BoardUI). But only code running on the JavaFX thread can update the JavaFX user interface. Unfortunately, the runMove call is now running on a new thread and so the JavaFX user interface does not update.

In order to fix this I needed to, within the new thread, schedule the runMove call to run on the JavaFX thread. Fortunately this can be done by wrapping the call in a lambda and passing it to Platform.runLater, The JavaFX docs explains thatPlatform.runLater will:

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.

Therefore all I needed to do was change:

move(nextPlayer.getMove(game));

to:

Platform.runLater(() -> move(nextPlayer.getMove(game)));

Final thoughts

This was an interesting problem to solve. At first I was thrown by the behaviour, as at that stage I had no idea about threading in Java or JavaFX.

But I was able to hone in on the problem by first confirming that the moves and delays were working in sequence. This gave me an idea of where the problem might be.

I am not sure whether my solution is the canonical approach, but it’s been an interesting first exposure to threads and given me a bit more insight into the workings of JavaFX.

--

--