Reputation: 2500
So far I've been building an application in which a worker thread, when it reaches a certain point or event, changes a StringProperty
with the millis
timestamp of the event.
In turn, a listener in the JavaFX Main Thread, listens to that property and outputs the timestamp to a TextArea
I have in the GUI.
In the process of my work I've had to split some functionality of the Worker thread to a second Worker thread, as it was a bit too slow.
The problem is this: Now that I've got two Workers that need to be "playing" with that StringProperty, I get ConcurrentModification
Exceptions, as expected.
Are there any ways or implementations to deal with that, or will I have to make the second worker use a second StringProperty (and, in turn, attach a second listener to my main thread, which is NOT preferrable)?
EDIT: For the gentleman that requested the listener, it's like this:
my_monitor.getMessageProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, final String newValue) {
//Substring because the last 13 digits is the current system milli time,
//the measure is in place so that the observableValue always changes on an event.
Platform.runLater(new Runnable() {
@Override
public void run() {
writeToLog(newValue.substring(0, newValue.length() - 13));
}
});
}
});
Upvotes: 1
Views: 1587
Reputation: 145
Many years later, the solution provided by @James_D works though there are better ways to handle Concurrency in JavaFX.
Best keep the JavaFX main application thread free of any heavy work(Or any work other than rendering the GUI for a smooth UX).
Create and run Tasks with the Task
class in JavaFX.
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class WorkerSharedPropertyExample extends Application {
final TextArea console = new TextArea();
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
final BorderPane root = new BorderPane();
TextField textField = new TextField();
root.setTop(textField);
root.setCenter(console);
final Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
Thread worker1 = createThread(1000000000);
worker1.start();
}
private Thread createThread(int timesToRun) {
Task<Integer> task = new Task<>() {
int sum = 0;
@Override
protected Integer call() throws InterruptedException {
for (int i = 0; i < timesToRun; i++) {
Thread.sleep(100);
sum += i;
// Keep the changes tracked.
updateValue(sum);
}
return sum;
}
};
task.valueProperty().addListener(
(observableValue, oldValue, newValue) -> console.appendText("The Numbers is: " + newValue + "\n")
);
Thread thread = new Thread(task);
thread.setDaemon(true);
return thread;
}
}
You can then create as many Tasks as you need to and your GUI will remain as responsive. The TextField
added is to get this tested, I can type there whilist the counter is updating my TextArea
values.
Sorry for the use of Lambdas.
Upvotes: 1
Reputation: 209673
Your second approach won't work anyway. If you modify the StringProperty
from your background thread, the listener which observes it will be invoked on that background thread, resulting in updates to your TextArea
from the background thread.
Instead, update the (single) StringProperty
on the FX Application Thread using Platform.runLater()
. Then your string property is only ever accessed from the FX Application Thread, so it's effectively single-threaded.
Update: Full example
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class WorkerSharedPropertyExample extends Application {
@Override
public void start(Stage primaryStage) {
final TextArea console = new TextArea();
final BorderPane root = new BorderPane();
root.setCenter(console);
final Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
final StringProperty messageHolder = new SimpleStringProperty();
messageHolder.addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> obs, String oldValue, String newValue) {
console.appendText(newValue + "\n");
}
});
Thread worker1 = createThread(messageHolder, "Worker 1");
Thread worker2 = createThread(messageHolder, "Worker 2");
worker1.start();
worker2.start();
}
private Thread createThread(StringProperty messageHolder, String name) {
final Random rng = new Random();
Thread thread = new Thread() {
@Override
public void run() {
int value = 0 ;
try {
while (true) {
Thread.sleep(rng.nextInt(1000)+500);
value++;
final String message = getName() + " updated status to value "+value ;
Platform.runLater(new Runnable() {
@Override
public void run() {
messageHolder.set(message);
}
});
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.setName(name);
thread.setDaemon(true);
return thread ;
}
public static void main(String[] args) {
launch(args);
}
}
Upvotes: 3