Nowadays some says it is not suitable to use Platform.runLater()
for updating the UI from a non-JavaFX Thread and Oracle site introduce a way with bindings for progress bar updating. Here I want to update a Label, so coded it like this:
Task task = new Task() {
@Override
protected Object call() throws Exception {
int i = 0;
while (true) {
this.updateMessage("Count " + i);
System.out.println(i);
// Thread.sleep(10);
i++;
}
}
};
Thread t = new Thread(task);
lbStatus.textProperty().bind(task.messageProperty());
t.start();
It works.
I want to know is it good enough or is there any other ways to consider?
Thank you.
I don't think it's quite true to say it's not "suitable" to use Platform.runLater(...)
to update the UI from a background thread. There are certainly cases where that is the correct thing to do, as shown in the Task
Javadocs. What the javafx.concurrent
API provides is a "higher-level" interface to the functionality you commonly need when you are writing multithreaded JavaFX applications. The classes in this package are written by people with a lot of expertise in multithreaded programming, so it's likely that they have accounted for subtleties that the average programmer may not be aware of.
As an example, while it is correct that updateMessage
eventually ends up calling Platform.runLater(...)
, the two are not completely equivalent. If you try the same thing with a naïve call to Platform.runLater(..)
:
// Don't do this! It will make the UI unresponsive:
Task task = new Task() {
@Override
protected Object call() throws Exception {
int i = 0;
while (true) {
Platform.runLater(() -> lblStatus.textProperty().set("Count "+i));
i++;
}
return null ;
}
};
Thread t = new Thread(task);
your UI will become (at least partially) unresponsive. The reason is that you're scheduling so many Runnable
s on the FX Application thread, it doesn't have time to do its regular work (rendering UI, responding to user input, etc). The implementation of updateMessage(...)
is carefully written to "throttle" the calls to Platform.runLater(...)
(it basically limits them to one per frame-rendering). That code is a little tricky to implement: using the javafx.concurrent
API as in your code example means you don't have to implement it yourself.
So changes to the UI must always be made on the FX Application Thread, and the way to schedule those changes is via Platform.runLater(...)
. In effect you either call that directly, or call code that ends up calling it. However, some of the API methods that wrap the calls to Platform.runLater(...)
do so in quite sophisticated ways, and when those methods provide the functionality you need, you should probably prefer those to making the calls yourself.
As updateMessage() on a Task finally uses Platform.runLater() to update the messageProperty, it is no alternative to a direct runLater() in your code. So the essence is:
JavaFX is not multithreaded, any Porperty binding will only work if the property is modified in the JavaFX thread, e.g. by using Platform.runLater().