JavaFX: Not in Application Thread only when updating a Label? - java

for the last few days I have been experimenting with JavaFX, FXML, Tasks and Properties. I stumbled upon a strange behaviour and hope that you can help me to understand better what's going on.
I have a minimalistic GUI which looks like this: GUI
If I click on the Button a new Task is created and started. This Task increments a Double Property and the new Value is written onto the Label and set in the ProgressBar. The Code of the Task can be seen here:
public class TestTask extends Task<Void>{
private final DoubleProperty doubleValue = new SimpleDoubleProperty();
#Override
protected Void call() throws Exception {
for(double i = 0.1; i <= 1; i = i+0.1 ) {
doubleValue.set(i);
Thread.sleep(1000);
}
return null;
}
public DoubleProperty test() {
return doubleValue;
}
}
The code of the FXML Controller is the following:
public class FXMLDocumentController implements Initializable {
#FXML private Label label;
#FXML private Slider slider;
//Called when the Button is clicked
#FXML
private void handleButtonAction(ActionEvent event) {
TestTask task = new TestTask();
task.test().addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
//Works without problems
slider.setValue(newValue.doubleValue());
//Throws an exception
label.setText("Value" + newValue.doubleValue());
});
new Thread(task).start();
}
}
If I run this code the attempt to update the Label results in the following exception: java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4. The update of the Slider works fine and throws no exptions.
After reading the following questions on SO:
On which thread JavaFX change listeners are executed ?
Using threads to make database requests
I understand that the update of the Label fails because I'm trying to execute an UI Update within the Listener which is called from the TestTask Thread and not from the FXApplication Thread.
My question is now: Why does the Update of the Slider work und doesn't throw an exception? The update is carried out within the Listener and therefore from within the TestTask Thread. Shouldn't this attempt also throw a "Not on FX application thread" exception?
Thanks in advance for taking your time to help me.

Updating a property on one thread while you listen for changes to the property on another thread is just a race condition. Don't do it.
Not only does the JavaFX system assume updates to the UI occur on a single thread, it also assumes the same for properties, regardless of whether they are bound to UI elements. Don't rely on the JavaFX system to check for all race conditions and report and handle them correctly for you, because it is not designed to do that.
You need write your code in such a way as to prevent such conditions.

Related

A pure non-UI non-Java FX thread still getting java.lang.IllegalStateException: Not on FX application thread

I saw many examples of errors involving "Not on FX application thread" and in all the examples I checked, it was always because one was trying to update a UI element from a non-FX tread. However, in my example, I am getting an error from a thread that has no JAVAFX nor UI code.
The high level summary of my code is that from the controller, I am starting a Task that executes a method anal1.intializeFileList() which is a pure calculation routine with no UI commands, as mentioned above. It only changes static observable variables in a class called Stats. These variables are binded to UI elements. Thus, the pure non-UI method communicates to the UI via the variables in the Stats class. I have used this successfully for updating 3 different ProgressBars and enabling and disabling a button and it has worked flawlessly. I decided to use the same methodology to simply update the text of a Label called initLabel. I do it only twice (and therefore I feel it is much less intense than the updating of the ProgressBars and enabling buttons) and it generates the error "Exception in thread "Thread-3" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3".
Any ideas why this error is happening, given that my thread is a non-UI and non-JAVAFX tread? How can I prevent the error from happening?
Here are the relevant declarations in my Controller:
#FXML
private ProgressBar populateProgress;
#FXML
private ProgressBar sortProgress;
#FXML
private ProgressBar dispoProgress;
#FXML
private Label initLabel;
Here is the non-UI task declared in the Controller:
Task<Void> startAnal = new Task<Void>() {
#Override
protected Void call() throws Exception {
anal1.intializeFileList();
return null;
}
};
Here is the starting of the task
new Thread(startAnal).start();
Here are my bindings in the Controller. Note all of the UI's work and all the bindings work except the one associated with initLabel on the last line!
#FXML
public void initialize() {
// Do bindings
startButton.disableProperty().bind(Bindings.createBooleanBinding(() -> {
// condition for not being able to start analysis
int bits012 = 1 | (1 <<1) | (1<<2);
if (Stats.canStart.getValue() != bits012)
{
// can not start because dir's not entered or analysis running
return true;
}
else {
// valid to start and all dir's entered and analysis is not running
return false;
}
},Stats.canStart));
populateProgress.progressProperty().bind(Stats.populate);
sortProgress.progressProperty().bind(Stats.sort);
dispoProgress.progressProperty().bind((Stats.dispo));
initLabel.textProperty().bind(Stats.status);
}
Here are some declarations in the Stats class:
static SimpleDoubleProperty populate = new SimpleDoubleProperty(0.00);
static SimpleDoubleProperty sort = new SimpleDoubleProperty(0.00);
static SimpleDoubleProperty dispo = new SimpleDoubleProperty(0.00);
static SimpleIntegerProperty canStart = new SimpleIntegerProperty(1<<2);
static SimpleBooleanProperty cancel = new SimpleBooleanProperty(false);
static SimpleStringProperty status = new SimpleStringProperty("Please Enter Directory options");
Here the first (of 2 total) places where the exception is generated (at the line containing Stats.status.set):
Stats.sort.set(0.00);
Stats.dispo.set(0.00);
Stats.status.set("Analysis is running");
// set not running to false
int running = Stats.canStart.get()&(~(1<<2));
Stats.canStart.set(running);
Here is the stack trace:
Exception in thread "Thread-3" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3
at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:291)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:446)
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:474)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
at javafx.controls/javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:272)
at javafx.controls/javafx.scene.control.skin.LabeledSkinBase.lambda$new$11(LabeledSkinBase.java:220)
at javafx.controls/com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler.lambda$new$1(LambdaMultiplePropertyChangeListenerHandler.java:49)
at javafx.base/javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:86)
at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
at javafx.base/javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:104)
at javafx.base/javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:111)
at javafx.base/javafx.beans.property.StringPropertyBase$Listener.invalidated(StringPropertyBase.java:231)
at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
at javafx.base/javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:104)
at javafx.base/javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:111)
at javafx.base/javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:145)
at dirComparer/sample.fileDB.intializeFileList(fileDB.java:371)
at dirComparer/sample.Controller$1.call(Controller.java:299)
at dirComparer/sample.Controller$1.call(Controller.java:296)
at javafx.graphics/javafx.concurrent.Task$TaskCallable.call(Task.java:1425)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.lang.Thread.run(Thread.java:832)

javafx textfield doesn't change

I have the following code:
#FXML
private void test(){
textField.setText("Pending...");
boolean passed = doStuff();
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
}
And what I tries to achieve is that while the doStuff() do his stuff in a textField in the GUI there should be written "Pending..." and as soon as it finish it should change to "OK" / "Error".
I want that the GUI is blocked while doStuff is running so the user has to wait and can't click something else.
But what happens is that as soon as I start test it does the doStuff() but only updates the textField with "OK"/"Error" but I never see "Pending...".
I have the feeling that I have somehow update the GUI, but I'm not sure how it should be done.
Update:
What I tried is to move the doStuff in another Thread:
#FXML
private void test(){
textField.setText("Pending...");
Thread t = new Thread(){
public void run(){
boolean passed = doStuff();
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
}
};
t.start();
t.join();
}
It would works if i would remove the t.join(); command, but then the UI wouldn't be blocked. So I'm at a loss right now.
Thanks
You must never run long running tasks on the JavaFX Application Thread. Doing so will prevent said thread from doing any GUI related things which results in a frozen UI. This makes your user(s) sad. However, your attempt at putting the long running task on a background task is flawed. You call Thread.join which will block the calling thread until the target thread dies; this is effectively the same thing as just running the task on the calling thread.
For a quick fix to your example, you could do the following:
#FXML
private void test(){
textField.setText("Pending...");
Thread t = new Thread(){
#Override public void run(){
boolean passed = doStuff();
Platform.runLater(() -> {
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
});
}
};
t.start();
}
That will create a thread, start it, and let it run in the background while letting the JavaFX Application Thread continue doing what it needs to. Inside the background thread you must update the TextField inside a Platform.runLater(Runnable) call. This is needed because you must never update a live scene graph from a thread other than the JavaFX Application Thread; doing so will lead to undefined behavior. Also, you should look into “implements Runnable” vs “extends Thread” in Java. It's better, or at least more idiomatic, to do:
Thread t = new Thread(() -> { /* background code */ });
You could also use a javafx.concurrent.Task which may make it easier to communicate back to the JavaFX Application Thread. One option would be:
#FXML
private void test(){
textField.setText("Pending...");
Task<Boolean> task = new Task<>() {
#Override protected Boolean call() throws Exception {
return doStuff();
}
};
task.setOnSucceeded(event -> textField.setText(task.getValue() ? "Ok" : "Error"));
new Thread(task).start();
}
You could also bind the TextField to the message property of the Task and call updateMessage("Pending...") inside the call method. You could even provide more detailed messages if and when possible.
That said, creating and starting Threads yourself is not ideal and you should look into thread pooling (using something like an ExecutorService). You might also want to look into javafx.concurrent.Service for "reusing" Tasks.
For more information about JavaFX concurrency see Concurrency in JavaFX and read the documentation of the classes in javafx.concurrent. For the basics of multi-threading in Java see Lesson: Concurrency from The Java™ Tutorials.

JavaFX: Console Log in a TextArea + Multithreading and Tasks

I'm trying to achieve a feast which many attempted here on StackOverflow: showing the console output of a Java application in a TextArea built on JavaFX.
I've managed to show my output in said TextArea but, as many others, my UI freezes, 'cause this thread is heavily loading the one used to show the UI itself.
So I've started reading about Platform.runLater(), but it doesn't solve my issue, mostly because I'm outputting a lot of text and this slows down said function. Looking around, I've got into this question, where a nice solution based on Task is proposed. Neverthless, my UI keeps freezing as soon as I start to show my console log into the TextArea. I'll show you a snippet of my code, so that you may be able to tell me what I'm missing and/or doing wrong.
This is a snippet of my JavaFX controller:
public class MainViewController extends AbstractController implements Initializable {
#FXML private TextArea textAreaLog;
#Override
public void initialize(URL location, ResourceBundle resources) {
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
boolean fxApplicationThread = Platform.isFxApplicationThread();
System.out.println("Is call on FXApplicationThread: " + fxApplicationThread);
Console console = new Console(textAreaLog);
PrintStream ps = new PrintStream(console, true);
System.setOut(ps);
System.setErr(ps);
return null;
}
#Override
protected void succeeded() {
boolean fxApplicationThread = Platform.isFxApplicationThread();
System.out.println("Is call on FXApplicationThread: " + fxApplicationThread);
super.succeeded();
textAreaLog.textProperty().unbind();
}
};
textAreaLog.textProperty().bind(task.messageProperty());
new Thread(task).start();
}
// Console Class
public static class Console extends OutputStream {
private TextArea output;
Console(TextArea ta) {
this.output = ta;
}
#Override
public void write(int i) throws IOException {
output.appendText(String.valueOf((char) i));
}
}
}
I've edited the code taken from answer to the question I've previously linked, leaving all the debug messages just to help me out.
That's all. My UI just freezes, even if I'm apparently running my heavy-load task in the background instead of doing that directly in my UI thread.
I think root of the problem is one of the below;
System.out.println("text") is being a synchronized method.
accesing ui component outside of Ui thread
When you call System.out.println("text") from ui thread, the synchronization on System.out will cause UI to freeze for duration of synchronization.
You can check if this is the cause like below;(You have to wrap all your System.out calls like below, for only to test if the above theory is correct)
This will cause println methods to synchronize in different thread.(common-pool threads)
CompletableFuture.runAsync(()->System.out.println("text"));
You should also update output component in ui thread.(Problem is solved with this in this case)
// or create new runnable if you are not using java8
Platform.runLater(()->output.appendText(String.valueOf((char) i)));

Updating JavaFX Controls While Running the App

I am using JavaFx for creating a Java Standalone Application.
I have seen some examples but I am not able to understand how to use the javaFX Task in my code scenario.
This is the Controller function which I am calling for Button onAction which I have set from SceneBuilder -->
public class MainScreenController {
#FXML
private JFXButton btnSelectImg;
#FXML
private ImageView imageViewObj;
#FXML
private ProgressBar progressBarObj;
//..
//..
#FXML
private void onFileSelectButtonClick() {
//Some Operations are carried out
//..
//Then I want to set Image in ImageView
imageViewObj.setImage(myImage);
// Some Code Here
//..
// Set Progress
progressBarObj.setProgress(0.1);
// Some Code Here
//..
// Set Progress
progressBarObj.setProgress(0.2);
//...
//...
// Maybe change some other Controls
//..........
}
//..
//..
}
Now here I am updating multiple controls in the same function gradually as the code progresses step by step but it gets updated at last when execution is done.
I want to update the controls while execution as shown in the code.
This is a likely a duplicate of bits of other questions:
JavaFx: Update UI label asynchronously with messages while application different methods execution
JavaFx ProgressBar doesnt update
Platform.runLater and Task in JavaFX
Usage of JavaFX Platform.runLater and access to UI from a different thread
And perhaps some other questions.
As an overall approach, you define a Task, then within the execution body of the Task, you make use of Platform.runLater(), updateProgress() and other mechanisms to achieve what you need. See the related question for further explanations of these mechanisms.
final ImageView imageViewObj = new ImageView();
Task<Void> task = new Task<Void>() {
#Override protected Void call() throws Exception {
//Some Operations are carried out
//..
//Then I want to set Image in ImageView
// use Platform.runLater()
Platform.runLater(() -> imageViewObj.setImage(myImage));
// Some Code Here
//..
// Set Progress
updateProgress(0.1, 1);
// Some Code Here
//..
// Set Progress
updateProgress(0.2, 1);
int variable = 2;
final int immutable = variable;
// Maybe change some other Controls
// run whatever block that updates the controls within a Platform.runLater block.
Platform.runLater(() -> {
// execute the control update logic here...
// be careful of updating control state based upon mutable data in the task thread.
// instead only use immutable data within the runLater block (avoids race conditions).
});
variable++;
// some more logic related to the changing variable.
return null;
}
};
ProgressBar updProg = new ProgressBar();
updProg.progressProperty().bind(task.progressProperty());
Thread thread = new Thread(task, "my-important-stuff-thread");
thread.setDaemon(true);
thread.start();

Building Multithreading ProgressBar in JavaFx

I am attempting to build a progress bar that is being updated as my application is retrieving and populating data to the GUI. I figured that the progress bar will be reused a lot so I decided to create its own class. However, I don't believe I understand either the Worker/Task or Multi-Threading in general to create a re-usable situation. What would be the recommended approach to creating a progress bar that can listen to the application thread and update the bar accordingly. Here is my attempt:
// Simple Progress Bar View as Pop Up
public class ProgressIndicatorUtil{
#FXML
private ProgressBar progressBar;
#FXML
private Label statusLabel;
#FXML
private Button closeButton;
#FXML
private Label valueLabel;
private Worker worker;
private Stage stage;
public void setPopUpStage(Stage stage) {
this.stage = stage;
}
public void setWorker(Worker worker) {
this.worker = worker;
}
public void setLinkToMainPage(Object controller) {
((Task<String>) worker).setOnSucceeded(event -> stage.close());
((Task<String>) worker).setOnCancelled(event -> {
closeButton.setVisible(true);
stage.requestFocus();
statusLabel.setTextFill(Color.RED);}
);
valueLabel.textProperty().bind(Bindings.format("%5.1f%%", worker.progressProperty().multiply(100)));
progressBar.progressProperty().bind(worker.progressProperty());
statusLabel.textProperty().bind(worker.messageProperty());
}
#FXML
private void handleClose(ActionEvent e){
stage.close();
}
}
The Controller that calls the View Pop-Up and runs the Progress Bar Thread.
public class MyController{
//Controller calling the view and disabling the main GUI
private void loadProgressBar(Worker worker){
try{
FXMLLoader loader = new FXMLLoader(getClass()
.getClassLoader().getResource("main/resources/fxml/ProgressBar.fxml"));
AnchorPane pane = (AnchorPane)loader.load();
Stage popUpStage = new Stage();
popUpStage.initModality(Modality.WINDOW_MODAL);
Scene scene = new Scene(pane);
popUpStage.setScene(scene);
ProgressIndicatorUtil controller = loader.getController();
controller.setPopUpStage(popUpStage);
controller.setWorker(worker);
controller.setLinkToMainPage(this);
mainPane.setDisable(true);
popUpStage.showingProperty().addListener((obs, hidden, showing) -> {
if(hidden) mainPane.setDisable(false);});
popUpStage.show();
}catch(IOException e){
e.printStackTrace();
}
}
private void runProgressBar(Worker worker) {
new Thread((Runnable) worker).start();
}
//A user action that runs the progress bar and GUI
#FXML
private void aBigProcessingEvent(ActionEvent event) {
Worker worker = new Task<String>(){
#Override
protected String call() throws Exception {
updateProgress(0, 3);
updateMessage("Clearing Data");
processingEvent01();
updateProgress(1, 3);
updateMessage("Retriving Data And Adding To List");
processingEvent02();
updateProgress(2, 3);
updateMessage("Populating Data");
processingEvent03();
updateProgress(3, 3);
return "Finished!";
}
};
loadProgressBar(worker);
runProgressBar(worker);
}
}
The program works fine, visually, but it throws an Exception like this (Not On FX Application Thread) and running Platform.runLater() on my "processingEvent" methods will cause my progress bar to be 100% immediately, but it won't throw anymore Exceptions. Any suggestion to how to split the application modification methods and the worker methods apart while keeping the progression connected to the processingEvent methods? Much thanks.
There is nothing wrong with the (incomplete) code you have posted, so there errors are in other parts of your code. Since the code is incomplete, I have to make some educated guesses as to what is happening. (Note: it is actually much better if you can create complete examples when you post questions, so that you ensure the cause of the issue you are asking about is included.)
Since you are getting an IllegalStateException "Not on the FX Application Thread", you must be updating the UI from a background thread. Since the only code you've posted that runs in a background thread is in the Task you create in aBigProcessingEvent(), the UI updates must be happening in the one or more of the processingEventXX() methods you haven't shown.
If you wrap the calls to processingEventXX() in Platform.runLater(), then you won't see any progressive updates to the progress bar. Platform.runLater() schedules the runnable you provide to be executed on the FX Application Thread and exits immediately. There is no other code in the Task that takes time to run, so the entire task is completed in very little time, and by the time the FX Application Thread renders the next frame, the task is complete and the progress property is at 1.
So presumably your processingEventXX() methods take time to execute, and also update the UI. You must wrap the calls that update the UI in those methods in Platform.runLater(...). The code wrapped in Platform.runLater(...) must not include code that takes a long time to run. I.e. they should look like
private void processingEvent01() {
// some long-running process here...
Platform.runLater(() -> {
// update UI here....
});
// some other long-running process here (perhaps)
}

Categories