Constantly Update UI in Java Fx Worker Thread

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();

Constantly Updating JavaFX GUI

Here is a working version.

import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class ClockView extends Application {
Pane background;
static Text firstDigit;
static Text secondDigit;
static Text thirdDigit;
static Text fourthDigit;
Text middleDecimal;

public final Timer clockTimer = new Timer();

/* Sets the first digit of the base 10 time to the passed through char. */
static void setFirstDigit(char x1) {
String digitString = "";
digitString += x1;
firstDigit.setText(digitString);
}

/* Sets the second digit of the base 10 time to the passed through char. */
static void setSecondDigit(char x2) {
String digitString = "";
digitString += x2;
secondDigit.setText(digitString);
}

/* Sets the third digit of the base 10 time to the passed through char. */
static void setThirdDigit(char y1) {
String digitString = "";
digitString += y1;
thirdDigit.setText(digitString);
}

/* Sets the fourth digit of the base 10 time to the passed through char. */
static void setFourthDigit(char y2) {
String digitString = "";
digitString += y2;
fourthDigit.setText(digitString);
}

/* Main Method that Launches the GUI */
public static void main(String[] args) {
launch(args);
}

@Override
public void start(Stage primaryStage) throws Exception {
final double TEXTFIELD_LAYOUT_Y = 200;

//Background Pane
background = new Pane();

//First digit textField
firstDigit = new Text("0");
firstDigit.setLayoutX(17);
firstDigit.setLayoutY(TEXTFIELD_LAYOUT_Y);
firstDigit.setStyle("-fx-font-size: 96pt;");

//Second digit textField
secondDigit = new Text("0");
secondDigit.setLayoutX(117);
secondDigit.setLayoutY(TEXTFIELD_LAYOUT_Y);
secondDigit.setStyle("-fx-font-size: 96pt;");

//Middle decimal
middleDecimal = new Text(".");
middleDecimal.setLayoutX(219);
middleDecimal.setLayoutY(210);
middleDecimal.setStyle("-fx-font-size: 72pt;");

//Third digit textField
thirdDigit = new Text("0");
thirdDigit.setLayoutX(250);
thirdDigit.setLayoutY(TEXTFIELD_LAYOUT_Y);
thirdDigit.setStyle("-fx-font-size: 96pt;");

//Fourth digit textField
fourthDigit = new Text("0");
fourthDigit.setLayoutX(362);
fourthDigit.setLayoutY(TEXTFIELD_LAYOUT_Y);
fourthDigit.setStyle("-fx-font-size: 96pt;");

/* Adding the Nodes to the Pane */
background.getChildren().addAll(firstDigit, secondDigit, middleDecimal, thirdDigit, fourthDigit);

/* Setting the Scene */
Scene scene = new Scene(new Group(), 470, 258);
Group root = (Group)scene.getRoot();
root.getChildren().add(background);
primaryStage.setTitle("Base 10 Clock");
primaryStage.setScene(scene);
primaryStage.show();

clockTimer.scheduleAtFixedRate(new TimerTask() {

Calendar now;
double currentTime;
String timeString;
long timestamp;

@Override
public void run() {
/*
* Calculates the time in base 10 time and calls the 4 methods
* to set the GUI display.
*
* In a constant while loop in order to continuously update
* the GUI.
*/
now = Calendar.getInstance();
timestamp = now.get(Calendar.HOUR_OF_DAY)*60*60 + now.get(Calendar.MINUTE)*60 + now.get(Calendar.SECOND);
currentTime = timestamp/86400.0;
timeString = "" + currentTime;
Platform.runLater(new Runnable() {
@Override public void run() {
setFirstDigit(timeString.charAt(2));
setSecondDigit(timeString.charAt(3));
setThirdDigit(timeString.charAt(4));
setFourthDigit(timeString.charAt(5));
}
});
}
}, 0, 8640 // Sleep for 8.64 seconds since that is how long it is between
); // increments of 0.01 in base 10 time.
}
}

Java Update UI from a Worker Thread

Use the Platform#runLater() static method, which runs the specified Runnable on the JavaFX Application Thread; it could be called from any thread:

Platform.runLater(() -> {
// The code that you want to run on the JavaFX Application Thread
});

java thread immediately update UI

Simple approach: block background thread until update is complete:

You need to update the UI on the FX Application Thread. Typically you do this by passing a plain Runnable to Platform.runLater(...).

If you want to wait for that ui update to complete before proceeding, instead create a FutureTask and pass it to Platform.runLater(...). Then you can call get() on the FutureTask, which will block until the task is complete:

private void updateUI() throws InterruptedException {

// actual work to update UI:
FutureTask<Void> updateUITask = new FutureTask(() -> {

// code to update UI...

}, /* return value from task: */ null);

// submit for execution on FX Application Thread:
Platform.runLater(updateUITask);

// block until work complete:
updateUITask.get();
}

This lets the FutureTask handle all the tricky work of waiting and notifying: it is always better to use a higher-level API for this kind of work when you can.

If you like, you can refactor this into a utility method, similarly to Dainesch's answer:

public class FXUtils {

public static void runAndWait(Runnable run) throws InterruptedException {
FutureTask<Void> task = new FutureTask<>(run, null);
Platform.runLater(task);
task.get();
}
}

Alternative approach: ensure that no more than one update is consumed during any frame rendering, blocking the background thread if an update is pending

Here is a somewhat different approach. Create a BlockingQueue with a capacity of 1 to hold the Runnables that update the UI. From your background thread, submit the Runnables to the blocking queue: since the blocking queue can hold at most one element, this will block if one is already pending.

To actually execute the updates in the queue (and remove them, so more can be added), use an AnimationTimer. This looks like:

private final BlockingQueue<Runnable> updateQueue = new ArrayBlockingQueue<>(1);

background thread code:

// do some computations...

// this will block while there are other updates pending:
updateQueue.put(() -> {
// code to update UI
// note this does not need to be explicitly executed on the FX application
// thread (no Platform.runLater()). The animation timer will take care of that
});

// do some more computations

Create the timer to consume the updates:

AnimationTimer updateTimer = new AnimationTimer() {

@Override
public void handle(long timestamp) {
Runnable update = updateQueue.poll();
if (update != null) {
// note we are already on the FX Application Thread:
update.run();
}
}
};

updateTimer.start();

This basically ensures that no more than one update is ever scheduled at any time, with the background thread blocking until any pending updates are consumed. The animation timer checks (without blocking) for pending updates on each frame rendering, ensuring that every update is executed. The nice thing about this approach is that you can increase the size of the blocking queue, effectively keeping a buffer of pending updates, while still ensuring no more than one update is consumed during any single frame rendering. This might be useful if there are occasional computations that take longer than others; it gives these computations a chance to be calculated while others are waiting to be executed.

Java FXML Updating UI with new Thread throws error

Updates to the UI, including calls to webEngine.executeScript(...) must be executed on the FX Application thread.

On the other hand, the FX Application Thread is (effectively) the thread used for rendering the UI and processing user input. So if you block this thread with an infinite loop, or other long running process, or if you schedule too many things to run on that thread, you will make the UI unresponsive.

What you are trying to do in your code appears to be to update the UI as fast as you can. If you put the loop in the FX Application Thread you will block it entirely: if you put it on a background thread and schedule the updates using Platform.runLater(...) you will flood the FX Application Thread with too many updates and prevent it from doing its usual work, and it will become unresponsive.

The general solution here revolves around the fact that it's really redundant to update the UI so often. The human eye can only detect visible changes at a limited rate, and in technology terms you are limited by, e.g. the refresh rate of the physical screen and of the underlying graphical software. JavaFX attempts to update the UI at no more than 60Hz (in the current implementation). So there's really no point in updating more often than the underlying JavaFX toolkit updates the scene.

The AnimationTimer provides a handle method that is guaranteed to be invoked once per scene update, no matter how frequently that occurs. AnimationTimer.handle(...) is invoked on the FX Application Thread, so you can safely make changes to the UI here. So you could implement your tracking with:

private AnimationTimer tracker ;

public void initialize() {
tracker = new AnimationTimer() {
@Override
public void handle(long timestamp) {

try {
double ar[] = FileImport.getGpsPosition();
// System.out.println("Latitude: " + ar[0] + " Longitude: " + ar[1]);
double Ltd = ar[0];
double Lng = ar[1];
webEngine.executeScript(""
+ "window.lat = " + Ltd + ";"
+ "window.lon = " + Lng + ";"
+ "document.goToLocation(window.lat, window.lon);");
} catch (IOException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}

}
};
}

@FXML
public void handleTracking() {
tracker.start();
}

The only thing to be wary of here is that, because handle() is invoked on the FX Application Thread, you should not perform any long-running code here. It looks as though your FileImport.getGpsPosition() method performs some IO operations, so it should probably be delegated to a background thread. The trick here, which is the one used by JavaFX classes such as Task, is to continually update a value from a background thread, and only schedule a call to Platform.runLater(...) if one is not already pending.

First, just define a simple class for representing the location (make it immutable so it is thread-safe):

class Location {
private final double longitude ;
private final double latitude ;

public Location(double longitude, double latitude) {
this.longitude = longitude ;
this.latitude = latitude ;
}

public double getLongitude() {
return longitude ;
}

public double getLatitude() {
return latitude ;
}
}

and now:

@FXML
private void handleTracking() {

AtomicReference<Location> location = new AtomicReference<>(null);

Thread thread = new Thread(() -> {
try {
while (true) {
double[] ar[] = FileImport.getGpsPosition();
Location loc = new Location(ar[0], ar[1]);

if (location.getAndSet(loc) == null) {
Platform.runLater(() -> {
Location updateLoc = location.getAndSet(null);
webEngine.executeScript(""
+ "window.lat = " + updateLoc.getLatitude() + ";"
+ "window.lon = " + updateLoc.getLongitude() + ";"
+ "document.goToLocation(window.lat, window.lon);");
});
}
}
} catch (IOException exc) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
});

thread.setDaemon(true);
thread.start();
}

The way this works is that it creates a (thread-safe) holder for the current location, and updates it as fast as possible. When it updates it, it (atomically) also checks if the current value is null. If it's null, it schedules a UI update via Platform.runLater(). If not, it simply updates the value but schedules no new UI update.

The UI update (atomically) gets the current (i.e. most recent) value and sets it to null, indicating it is ready to receive a new UI update. It then processes the new update.

This way you "throttle" the UI updates so that new ones are only scheduled when the current one is being processed, avoiding flooding the UI thread with too many requests.

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).

JavaFX Update UI With Arrays With Loops Periodically

This example uses Timeline. You can use Timeline in a loop-type way. Comments are in the code.

Main

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
*
* @author sedj601
*/
public class App extends Application
{

@Override // Override the start method in the Application class
public void start(Stage primaryStage)
{
List<List<Bar>> barsList = readFileData();//Get data from file.

//Create GUI Nodes.
Label lblYear = new Label();
List<Label> lblPlaces = new ArrayList();
List<Label> lblDataPoints = new ArrayList();
for(int i = 0; i < barsList.get(0).size(); i++)
{
Label lblTempPlaces = new Label();
lblPlaces.add(lblTempPlaces);

Label lblTempDataPoints = new Label();
lblDataPoints.add(lblTempDataPoints);
}

//Use Timeline to loop through the data and update the Labels.
AtomicInteger counter = new AtomicInteger();
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(1), (ActionEvent event) -> {
List<Bar> currentBars = barsList.get(counter.getAndIncrement());

for(int i = 0; i < currentBars.size(); i++)
{
lblPlaces.get(i).setText(currentBars.get(i).getPlace());
lblDataPoints.get(i).setText(Integer.toString(currentBars.get(i).getDataPoint()));
lblYear.setText(currentBars.get(i).getYear());
}
}));
timeline.setCycleCount(barsList.size());
timeline.play();

//Add Nodes to Parent Nodes. Add Parent Nodes to the Scene
VBox vbPlaces = new VBox();
vbPlaces.getChildren().addAll(lblPlaces);
VBox vbDataPoints = new VBox();
vbDataPoints.getChildren().addAll(lblDataPoints);
HBox hBox = new HBox(vbPlaces, vbDataPoints);
BorderPane root = new BorderPane();
root.setTop(lblYear);
root.setCenter(hBox);
Scene scene = new Scene(root, 300, 300);
primaryStage.setTitle("Hello World!"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
primaryStage.setResizable(false);
}

/**
* The main method is only needed for the IDE with limited JavaFX support.
* Not needed for running from the command line.
*/
public static void main(String[] args)
{
launch(args);
}

//Read the data from the file into a List of List<Bar>.
public static List<List<Bar>> readFileData()
{
List<List<Bar>> barsList = new ArrayList();

List<String> places = new ArrayList();
places.add("Vijayanagar");
places.add("Cairo");
places.add("Hangzhou");
places.add("Tabriz");
places.add("Gauda");
places.add("Istanbul");
places.add("Paris");
places.add("Guangzhou");
places.add("Nanjing");

Random random = new Random();
for(int i = 0; i < 7; i++)
{
List<Bar> bars = new ArrayList();
for(int t = 0; t < places.size(); t++)
{
bars.add(new Bar("150" + i, places.get(t), random.nextInt(700)));
}
Collections.sort(bars);
barsList.add(bars);
}

return barsList;
}
}

Bar

/**
*
* @author sedrick
*/
public class Bar implements Comparable<Bar>{
private String year;
private String place;
private Integer dataPoint;

public Bar(String year, String place, Integer dataPoint) {
this.year = year;
this.place = place;
this.dataPoint = dataPoint;
}

public Integer getDataPoint() {
return dataPoint;
}

public void setDataPoint(Integer dataPoint) {
this.dataPoint = dataPoint;
}

public String getYear() {
return year;
}

public void setYear(String year) {
this.year = year;
}

public String getPlace() {
return place;
}

public void setPlace(String place) {
this.place = place;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Bar{year=").append(year);
sb.append(", place=").append(place);
sb.append(", dataPoint=").append(dataPoint);
sb.append('}');
return sb.toString();
}

@Override
public int compareTo(Bar bar)
{
return bar.getDataPoint().compareTo(this.getDataPoint());
}
}

Sample Image

JavaFX make threads wait and threadsave gui update

The (main) problem with your code is that you are calling latch.await(), which is a blocking method, on the JavaFX Application Thread. Since the JavaFX Application Thread is responsible for updating the UI, this prevents the UI from being redrawn until latch.await() releases.

The basic premise of your question is wrong: you never want to make the UI thread pause, as it will always render the UI unresponsive and prevent any updates. Instead, you should think in terms of "performing a unit of work" in the background, potentially with updates to the UI as it proceeds, and then doing something in response to the background work completing.

Another potential issue with your code is that you are submitting a vast number of Runnables to the FX Application Thread via Platform.runLater(). You probably need to throttle these so that they don't "flood" the FX Application Thread.

You can solve all these issues using the Task API. The Task class is an implementation of Runnable whose call() method is invoked from the run() method. It has various updateXXX methods, including updateProgress(), that update various properties on the FX Application thread, and throttle these calls so that no more are scheduled than the FX Application thread can handle. Finally, it has callback methods, such as setOnSucceeded() that are invoked on the FX Application Thread when the background work completes (or, generally, when the task changes its lifecycle state).

(Note: I renamed your classes so they conform to recommended naming conventions. Like most Java developers, I find it extremely difficult to read code that doesn't conform to these.)

public class Test {
final ProgressBar bar = new ProgressBar(0.0);
TextArea area = new TextArea();
int result;
final Worker work = new Worker();
final Worker2 work2 = new Worker2();

// GUI Scene change happens here

work.setOnSucceeded(e -> work2.doSomething(work.getValue(), area));
bar.progressProperty().bind(work.progressProperty());
new Thread(work).start();

}
public class Worker extends Task<Integer> {
@Override
protected Integer call(){
for(int i = 1; i <= 1000000; i++)
updateProgress(i, 1000000);
return 51;
}
}
public class Worker2{
ArrayList<String> list = new ArrayList<>();

// this is now executed on the FX Application Thread: there is no need
// for Platform.runLater():

public int doSomething(int index, TextArea area){
area.append(list.get(index));
}
}

Your second example is a little more complicated, and I'm not really sure you need the additional threads at all: the only thing your Worker3 seems to do is append a line to a text area, which has to be done on the FX Application Thread anyway. But in case your real application would need background work for each file, this is what it would look like. I would recommend using a task pool for this instead of creating so many tasks by hand. This would look something like:

public class Test {

final ProgressBar bar = new ProgressBar(0.0);
TextArea area = new TextArea();
int result;
final Worker work = new Worker();
final Worker2 work2 = new Worker2();

final ExecutorService exec = Executors.newCachedThreadPool();

// This already has values
// it is not really a file array list in the map, but it is easier to show it this way
Map<String, ArrayList<File>> mapTypes;

// GUI Scene change happens here

work.setOnSucceeded(e -> {
work2.doSomething(work.getValue(), area);

Task<Void> processAllFiles = new Task<Void>() {
@Override
protected Void call() throws Exception {
final CountDownLatch latch2 = new CountDownLatch(Map.keySet().size());
mapTypes.keySet().forEach((String s) -> {
exec.submit(() -> {
work3.doSomething(mapTypes.get(s), area);
latch2.CountDown();
});
});
latch2.await();
return null ;
}
};

processAllFiles.setOnSucceeded(evt -> {
// executed on fx application thread:
System.out.println("Done");
});
});

bar.progressProperty().bind(work.progressProperty());
exec.submit(work);
}


Related Topics



Leave a reply



Submit