Updating UI from Different Threads in Javafx

Updating UI from different threads in JavaFX

Not sure if I completely understand, but I think this may help.

Using Platform.runLater(...) is an appropriate approach for this.

The trick to avoiding flooding the FX Application Thread is to use an Atomic variable to store the value you're interested in. In the Platform.runLater method, retrieve it and set it to a sentinel value. From your background thread, update the Atomic variable, but only issue a new Platform.runLater if it's been set back to its sentinel value.

I figured this out by looking at the source code for Task. Have a look at how the updateMessage method (line 1131 at the time of writing) is implemented.

Here's an example which uses the same technique. This just has a (busy) background thread which counts as fast as it can, updating an IntegerProperty. An observer watches that property and updates an AtomicInteger with the new value. If the current value of the AtomicInteger is -1, it schedules a Platform.runLater.

In the Platform.runLater, I retrieve the value of the AtomicInteger and use it to update a Label, setting the value back to -1 in the process. This signals that I am ready for another UI update.

import java.text.NumberFormat;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class ConcurrentModel extends Application {

@Override
public void start(Stage primaryStage) {

final AtomicInteger count = new AtomicInteger(-1);

final AnchorPane root = new AnchorPane();
final Label label = new Label();
final Model model = new Model();
final NumberFormat formatter = NumberFormat.getIntegerInstance();
formatter.setGroupingUsed(true);
model.intProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(final ObservableValue<? extends Number> observable,
final Number oldValue, final Number newValue) {
if (count.getAndSet(newValue.intValue()) == -1) {
Platform.runLater(new Runnable() {
@Override
public void run() {
long value = count.getAndSet(-1);
label.setText(formatter.format(value));
}
});
}

}
});
final Button startButton = new Button("Start");
startButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
model.start();
}
});

AnchorPane.setTopAnchor(label, 10.0);
AnchorPane.setLeftAnchor(label, 10.0);
AnchorPane.setBottomAnchor(startButton, 10.0);
AnchorPane.setLeftAnchor(startButton, 10.0);
root.getChildren().addAll(label, startButton);

Scene scene = new Scene(root, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}

public static void main(String[] args) {
launch(args);
}

public class Model extends Thread {
private IntegerProperty intProperty;

public Model() {
intProperty = new SimpleIntegerProperty(this, "int", 0);
setDaemon(true);
}

public int getInt() {
return intProperty.get();
}

public IntegerProperty intProperty() {
return intProperty;
}

@Override
public void run() {
while (true) {
intProperty.set(intProperty.get() + 1);
}
}
}
}

If you really want to "drive" the back end from the UI: that is throttle the speed of the backend implementation so you see all updates, consider using an AnimationTimer. An AnimationTimer has a handle(...) which is called once per frame render. So you could block the back-end implementation (for example by using a blocking queue) and release it once per invocation of the handle method. The handle(...) method is invoked on the FX Application Thread.

The handle(...) method takes a parameter which is a timestamp (in nanoseconds), so you can use that to slow the updates further, if once per frame is too fast.

For example:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;

public class Main extends Application {
@Override
public void start(Stage primaryStage) {

final BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(1);

TextArea console = new TextArea();

Button startButton = new Button("Start");
startButton.setOnAction(event -> {
MessageProducer producer = new MessageProducer(messageQueue);
Thread t = new Thread(producer);
t.setDaemon(true);
t.start();
});

final LongProperty lastUpdate = new SimpleLongProperty();

final long minUpdateInterval = 0 ; // nanoseconds. Set to higher number to slow output.

AnimationTimer timer = new AnimationTimer() {

@Override
public void handle(long now) {
if (now - lastUpdate.get() > minUpdateInterval) {
final String message = messageQueue.poll();
if (message != null) {
console.appendText("\n" + message);
}
lastUpdate.set(now);
}
}

};

timer.start();

HBox controls = new HBox(5, startButton);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);

BorderPane root = new BorderPane(console, null, null, controls, null);
Scene scene = new Scene(root,600,400);
primaryStage.setScene(scene);
primaryStage.show();
}

private static class MessageProducer implements Runnable {
private final BlockingQueue<String> messageQueue ;

public MessageProducer(BlockingQueue<String> messageQueue) {
this.messageQueue = messageQueue ;
}

@Override
public void run() {
long messageCount = 0 ;
try {
while (true) {
final String message = "Message " + (++messageCount);
messageQueue.put(message);
}
} catch (InterruptedException exc) {
System.out.println("Message producer interrupted: exiting.");
}
}
}

public static void main(String[] args) {
launch(args);
}
}

Javafx Updating UI from a Thread Java 8

You can add a Runnable parameter to your method. This parameter is given to you Platform.runLater:

private void startUpdateDaemonTask(Runnable runner) {
Task task = new Task<Void>() {
@Override
protected Void call() throws Exception {
while (true) {
Platform.runLater(runner);
Thread.sleep(1000);
}
}
};

Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}

Now you can invoke this method with your method references:

startUpdateDaemonTask(this::startUpdateDaemon);
startUpdateDaemonTask(this::updateGameStatus);

Update JavaFX UI from another thread

I'm running into a similar issue, as far as I can tell you have to deal with the error handling yourself. My solution is to update the UI via a method call:

Something like:

  try
{
//blah...
}
catch (Exception e)
{
reportAndLogException(e);
}
...
public void reportAndLogException(final Throwable t)
{
Platform.runLater(new Runnable() {
@Override public void run() {
//Update UI here
}
});
}

Essentially I am just manually moving it back to the UI Thread for an update (as I would do in pretty much any other framework).

Updating an ImageView from another thread javafx

You should think of the start() method as the application entry point, not the main(...) method, and so you should launch your other threads (that control the "machinery") from start(), not from main(). That way you don't even have the issue of retrieving the reference to the controller in main() (which you basically cannot do, since you can't get the reference to the Application subclass instance there). The main() method should simply bootstrap the launch of the JavaFX toolkit by calling Application.launch(), and do nothing else. (Note that in some deployment scenarios, your main(...) method is not even called, and the Application subclass's start() method is invoked by other mechanisms.)

So refactor as follows:

public class Main { // or whatever you called it...
public static void main(String[] args) {
Application.launch(Display.class, args);
}
}

Then in start:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Display extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(loader.load(), 800, 400));
primaryStage.show();

Controller controller = loader.getController();

Thread machineryThread = new Thread(() -> {
// some processes launching after the GUI, including updating the label
// which you can now easily do with
Platform.runLater(() -> controller.setLabel("Some new text"));
});
machineryThread.start();
}

}

If you want to separate the machinery completely from the UI (which is probably a good idea), it's not too hard to do this. Put the machinery in another class. The update of the label is effectively something that consumes (processes) a String (and for updating the image, it might consume some other kind of data). You can make this abstraction by representing it as a java.util.Consumer<String>. So you could do

public class Machinery {

private final Consumer<String> textProcessor ;

public Machinery(Consumer<String> textProcessor) {
this.textProcessor = textProcessor ;
}

public void doMachineryWork() {
// all the process here, and to update the label you do
textProcessor.accept("Some new text");
// etc etc
}
}

Note this class is completely independent of the UI. Your start(..) method would now be

  public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(loader.load(), 800, 400));
primaryStage.show();

Controller controller = loader.getController();

Machinery machinery = new Machinery(text ->
Platform.runLater(() -> controller.setLabel(text)));

Thread machineryThread = new Thread(machinery::doMachineryWork);
machineryThread.start();
}

Depending on other aspects of how your application is structured, it might also make sense to start the machinery thread from the controller's initialize() method, instead of from the start() method.

Modifying JavaFX gui from different thread in different class

You can still use your standalone class.

The rule is that changes to the UI must happen on the FX Application Thread. You can cause that to happen by wrapping calls from other threads that change the UI in Runnables that you pass to Platform.runLater(...).

In your example code, d.addElement(i) changes the UI, so you would do:

class Thread2 implements Runnable {
private DifferentThreadJavaFXMinimal d;

Thread2(DifferentThreadJavaFXMinimal dt){
d=dt;
}

@Override
public void run() {
for (int i=0;i<4;i++) {
final int value = i ;
Platform.runLater(() -> d.addElement(value));
// presumably in real life some kind of blocking code here....
}
System.out.println("Hahó");
}

}

How to run two JavaFX UI in actual different threads

You can't control the threading system for JavaFX, it's single threaded from an application point of view.

The JavaFX application thread is created by the JavaFX system. Read about how this works in the JavaFX architecture overview. Further background information is in the answer to: Javafx: Difference between javafx.concurent and Platform.runLater? and Multithreaded toolkits: A failed dream?

Comments on additional points in your question

When you say "we can run javafx UI updates in a new thread", that is not really true, because you are invoking Platform.runLater which shifts the work on to the JavaFX application thread, so you aren't really running updates in a new thread. You also state that when you call Platform.runLater from multiple threads "The problem is the two stages never update simultaneously" - which is exactly what the documentation for Platform.runLater says it will do "Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future" - so there are no sequencing guarantees when you utilize Platform.runLater.

I want to Update these two stages at the same time

Rather than creating your own threads, update the scene graphs for both stages in code executing sequentially on the JavaFX application thread (e.g. in the start method or an event handler for your application). Once your application is executing instructions on the JavaFX application thread, all application instructions will be executed before the next "pulse" of the JavaFX thread which renders the modified scene graph - so from a user point of view the two stages will update simultaneously as the pulse renders them. By default, JavaFX will issue rendering pulses sixty times a second. If you do a lot of work on the JavaFX application thread, then there will be a pause before the next pulse occurs (which is why it is advised that you get all of the work done in less than a sixtieth of a second).

If you have to do a lot of work (e.g. some I/O or incredibly computationally expensive task which will take a quarter of a second or more), you will need to make use of the JavaFX concurrency tools referenced earlier to ensure you aren't freezing your UI, then only update the UI once all concurrent tasks have completed - various techniques of which demonstrated in the answer to: How to reset progress indicator between tasks in JavaFX2?

Constantly Update UI in Java FX worker thread

you need to make changes to the scene graph on the JavaFX UI thread.
like this:

Task task = new Task<Void>() {
@Override
public Void call() throws Exception {
int i = 0;
while (true) {
final int finalI = i;
Platform.runLater(new Runnable() {
@Override
public void run() {
label.setText("" + finalI);
}
});
i++;
Thread.sleep(1000);
}
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();

Accessing JavaFX UI elements from a different thread?

Whenever needing to update a UI element from a thread, you must get the JavaFX UI thread to do so. Attempting to update an element from a different thread may lead to an exception, but could also lead to some unexpected behaviour.

Fortunately, JavaFX includes a useful way to do this. Simply add this in the code that runs on your separate thread:

Platform.runLater(() -> {
msgArea.setText("Your text");
});

It is better to use a task or service, because those provide in-built means of updating UI elements. For example, a task allows one to call updateMessage("...") or updateProgress("..."), which updates a bound element without you even needing to call the UI thread.



Related Topics



Leave a reply



Submit