Concurrency in JavaFX
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.