The basic rules for threading in JavaFX (and forgive me if you already understand some of this, I just want to be complete) are:
- Anything that blocks execution (or takes a long time to execute) should be run on a background thread - not on the FX Application Thread
- Anything that changes the state of a
Node
that is part of the scene graph should be executed on the FX Application Thread
In order to help achieve these, the JavaFX API provides a Task
class. This has a call()
method that returns a value; it is a Runnable
so can be provided as the argument to a Thread
constructor, or passed to an Executor
. It also provides useful callbacks that are guaranteed to be executed on the FX Application Thread, such as setOnSucceeded
, setOnFailed
, and various update...()
methods that update properties such as progress
and message
on the FX Application Thread.
However, the Task
class is really designed for one-off tasks: think about applications that need to retrieve data from a database, for example, which may take time. These execute a specific action and return a result. Your case is somewhat different, in that your thread is executing continuously.
In this case, it's probably better to use a simple Thread
and use Platform.runLater(...)
to update the UI. Platform.runLater(...)
takes a Runnable
and executes its run()
method on the FX Application Thread.
It's not clear to me why your code is behaving the way you describe, but assuming the method call snake.updatePosition()
causes a change in the UI, that should be executed on the FX Application Thread. In any case I would try
taskThread = new Thread(new Runnable() {
public void run() {
while(!Thread.currentThread().isInterrupted()){
Platform.runLater(new Runnable() {
@Override
public void run() {
snake.updatePosition();
}
});
try{
Thread.sleep(1000);
} catch(InterruptedException e){
break;
}
}
}
});
If you are using Java 8, this all looks a lot nicer with lambdas replacing the anonymous inner classes:
taskThread = new Thread( () -> {
while (! Thread.currentThread().isInterrupted()) {
Platform.runLater( snake::updatePosition );
try {
Thread.sleep(1000);
} catch (InterruptedException exc) {
break ;
}
}
});
One other technique in JavaFX for executing something periodically is to (ab?)use an Animation:
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1),
new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
snake.updatePosition();
}
}
));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
or, in Java 8, the somewhat slick
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1),
event -> snake.updatePosition()));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();