I have a single scene JavaFX app, that runs an animation, the animation needs to switch back and forth between two states.
Based on an example in "Java How to Program, 11/e, Early Objects"
I have written a controller that creates a similar setup in the initialization method,
And a task with boolean value that signals the animation timer when to switch states by fliping its value and then sleep.
I keep getting "java.lang.IllegalStateException: Task must only be used from the FX Application Thread" thrown from the call()
method no metter what I do.
here is a simplified version of the controller:
public class AnimationController {
@FXML public AnimationTimer myAnimationTimerExtention;
private ExecutorService executorService;
public void initialize() {
myAnimationTimerExtention.setState(false);
TimeingTask task = new TimeingTask (Duration.ofSeconds(5));
task .valueProperty().addListener((observableValue, oldValue, newValue) -> {
myAnimationTimerExtention.setState(false);
});
executorService = Executors.newFixedThreadPool(1);
executorService.execute(timeTrafficLightTask);
executorService.shutdown();
}
And here is my task:
public class TimeingTask extends Task<Boolean> {
private final Duration duration;
public TimeingTask(Duration duration) {
this.duration = duration;
updateValue(true);
}
@Override
protected Boolean call() throws Exception {
while (!isCancelled()) {
updateValue(!getValue());
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
updateMessage("timing task got interrupted");
}
}
return true;
}
}
I have tried so far:
Thread.currentThread().getName()
I'm on the application theard in the controller.updateValue()
in a Platform.runLater()
the excption is thrown on the Theard.sleep()
in all of the above scenarios the excption was thrown
The exception is being thrown because you can only directly access the value
property from the FX Application Thread. You can call updateValue()
from any thread, but you cannot call getValue()
from a background thread. If you inspect your stack trace carefully, you should see something like:
java.lang.IllegalStateException: Task must only be used from the FX Application Thread
at javafx.concurrent.Task.checkThread()
at javafx.concurrent.Task.getValue()
at yourpackage.TimeingTask.call()
meaning that the exception is thrown by the call to getValue()
(not the call to updateValue()
) in your call()
method.
The implementation of Task.getValue()
is
@Override public final V getValue() { checkThread(); return value.get(); }
with
private void checkThread() {
if (started && !isFxApplicationThread()) {
throw new IllegalStateException("Task must only be used from the FX Application Thread");
}
}
And, indeed, the documentation explicitly states
Because the
Task
is designed for use with JavaFX GUI applications, it ensures that every change to its public properties, as well as change notifications for state, errors, and for event handlers, all occur on the main JavaFX application thread. Accessing these properties from a background thread (including thecall()
method) will result in runtime exceptions being raised.
(My emphasis.)
So when you invoke getValue()
from your call()
method (which of course is run on the background thread), it throws the IllegalStateException
.
Threads are greatly overkill for what you're trying to do here anyway. As described here, you can greatly simplify this with a Timeline
:
public void initialize() {
myAnimationTimerExtention.setState(false);
KeyFrame frame = new KeyFrame(Duration.ofSeconds(5), e ->
myAnimationTimerExtention.setState(! myAnimationTimerExtention.getState()));
Timeline timeline = new Timeline(frame);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}
Or just build the functionality directly into your AnimationTimer
, which could easily check the timestamp passed to the handle()
method and flip the state if needed.
This gets rid of the need for your Task
subclass, the Executor
, and means the application doesn't consume additional resources by creating an additional thread.
Just in case the code in the question is a simplification, and you really need to use a background thread for some unknown reason, just shadow the value. To emphasize: do not add all the complexity of multithreading unless you need it. The solution above is a far better solution for the functionality you described.
public class TimeTrafficLightTask extends Task<Boolean> {
private final Duration duration;
private boolean state = true ;
public TimeTrafficLightTask(Duration duration) {
this.duration = duration;
updateValue(state);
}
@Override
protected Boolean call() throws Exception {
while (!isCancelled()) {
state = !state ;
updateValue(state);
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
updateMessage("timing task got interrupted");
}
}
return true;
}
}