Creating and stopping threads in JavaFX - java

I have a task to work with threads and JavaFX library. A separate thread 1 need to be started by pressing a button. The thread is supposed to fulfill a list with Integer values (100 - 150) with a small interval (e.g. 5 ms).
And after values are generated, it should stop. A new thread 2 must start and fill a ViewList with generated values.
But I get an exception each time when a new value is added to my list by thread 1:
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:204)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:364)
at javafx.scene.Scene.addToDirtyList(Scene.java:485)
at javafx.scene.Node.addToSceneDirtyList(Node.java:424)
at javafx.scene.Node.impl_markDirty(Node.java:415)
I tried to use Platform.runLater() in button event listener instead of creating a new Thread, but the program stops responding in that case.
Could anyone please help me, how to fill a collection with values in a separate thread and how to start a second one to update ViewList element after the first thread finishes?
Here is my class:
int itemName = 100;
Button btnOne = new Button("Button one");
Label label = new Label("Press the button to start");
ObservableList<String> listOptions = FXCollections.observableArrayList();
ListView<String> list;
#Override
public void start(Stage primaryStage) throws Exception{
primaryStage.setTitle("Hurry");
list = new ListView<>(listOptions);
list.setPrefSize(120, 120);
MultipleSelectionModel<String> lvSelModel =
list.getSelectionModel();
FlowPane rootNode = new FlowPane(10, 10);
rootNode.setAlignment(Pos.CENTER);
Scene myScene = new Scene(rootNode, 180, 200);
primaryStage.setScene(myScene);
lvSelModel.selectedItemProperty().addListener(
(changed, oldValue, newValue) -> {
label.setText("List option selected: " + newValue);
});
rootNode.getChildren().addAll(btnOne, list, label);
primaryStage.show();
Task<Integer> threadOne = new Task<Integer>(){
#Override
protected Integer call() throws Exception{
while(itemName < 130){
final int finalValue = itemName++;
listOptions.add(String.valueOf(itemName));
Platform.runLater(
() -> label.setText("Generating: " + finalValue));
Thread.sleep(100);
}
label.setText("Thread 1 finished");
return itemName;
}
};
btnOne.setOnAction(
ae -> {
Thread th = new Thread(threadOne);
th.setDaemon(true);
th.start();
});
}
public static void main(String[] args) {
launch(args);
}}`
The result program should be like this:
Thanks everyone for your time!

Since listOptions is the list of items in the list view, calling listOptions.add(...) modifies the UI (the list view). Thus it needs to be executed on the FX Application Thread, similarly to setting the text of the label. Just move that line in side the Platform.runLater(...):
Task<Integer> threadOne = new Task<Integer>(){
#Override
protected Integer call() throws Exception{
while(itemName < 130){
final int finalValue = itemName++;
Platform.runLater(() -> {
listOptions.add(String.valueOf(itemName));
label.setText("Generating: " + finalValue);
});
Thread.sleep(100);
}
label.setText("Thread 1 finished");
return itemName;
}
};
To start another task (or, indeed, do anything) when the first task finishes, use the task's onSucceeded handler:
threadOne.setOnSucceeded(e -> {
// create another task and run it in another thread...
});

Related

What operations should I put into Platform.runLater()?

Lately I encountered some issues with code that asynchonously updates GUI. I then came across this article, which shined some light on the problem - I was not using Platform.runLater() to update my GUI components, however, consider the original code:
public class Main extends Application {
private TextArea textArea = new TextArea();
private Label statusLabel = new Label("Not Started...");
private Button startButton = new Button("Start");
private Button exitButton = new Button("Exit");
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(final Stage stage) {
startButton.setOnAction(event -> startTask());
exitButton.setOnAction(event -> stage.close());
HBox buttonBox = new HBox(5, startButton, exitButton);
VBox root = new VBox(10, statusLabel, buttonBox, textArea);
root.setStyle("-fx-padding: 10;" +
"-fx-border-style: solid inside;" +
"-fx-border-width: 2;" +
"-fx-border-insets: 5;" +
"-fx-border-radius: 5;" +
"-fx-border-color: blue;");
Scene scene = new Scene(root, 400, 300);
stage.setScene(scene);
stage.setTitle("A simple Concurrency Example");
stage.show();
}
private void startTask() {
Runnable task = this::runTask;
Thread backgroundThread = new Thread(task);
backgroundThread.setDaemon(true);
backgroundThread.start();
}
private void runTask() {
for (int i = 1; i <= 10; i++) {
try {
String status = "Processing " + i + " of " + 10;
statusLabel.setText(status);
textArea.appendText(status + "\n");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
The problematic part is the runTask() method. The article explains that instead of simply using statusLabel.setText(status), I should use Platform.runLater(() -> statusLabel.setText(status));. That makes sense to me.
What doesn't make sense, however, is why I don't have to apply the same logic to textArea updates? Notice the fifth line of runTask() method - the textArea.appendText(status + "\n"); part. Why it doesn't give me an exception (java.lang.IllegalStateException: Not on FX application thread) about modifying an FX component from non-FX thread, since it's clearly a way of updating GUI FX component? What operations should I put inside the Platform.runLater() and what operations do now have to be there?
Run later will interfere more or less with the UI Thread. The execution will be queued in the UI thread. When you have a thread sleep or a long calculation the UI will freeze. You can use it for simple tasks. But in your case I do recommend it, because it is only a "set-operation".
Generally you should use Observable properties and bind them to the UI when possible. Then you don't need to do anything of the "Thread" and "Queuing" and "RunLater" operations.
When the WHOLE operation is needed to executed with the "run later", and the calculation would be time costly, you should look, for what you can run later or use a different Mechanism.
But when you just add something with out run laterform a difficult or longer calculation from another thread, an exception will pop up.
So what you want is to update the UI-Elements when it is needed and the new values are calculated or provided by whatever mechanism.
Here the way to go with Properties:
StringProperty text = new SimpleStringProperty("Hello");
Label label = new Label();
label.getTextProperty().bind(text);
Now whenever you Update text from another Thread, the update will automatically occur in the UI without Run Later.
If there is no way around:
What you could (in the case of more costly operations) do is have a queue that contains all the executions of updates for the view that come asynchronously. And execute the changes only in the JavaFX Thread.
You should use the Animationtimer for that.
//thread safe queue
Queue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
Now the approach with runLater:
Platform.runLater(()->{
while(!queue.isEmpty()) {
queue.remove().run();
}
});
And the approach with the AnimationTimer.
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
//Whatever condition or how many changes you would make at one tick
//In this case we just run all "updates" in that tick.
// I would recommend it, you could update 100 things each tick
while(!queue.isEmpty()) {
queue.remove().run();
}
}
};
timer.start();
Then have a thread that would calculate something and when it's done it adds the new change to the queue. Or sleeps or waits for a response etc.
private void startTask() {
Runnable task = this::runTask;
Thread backgroundThread = new Thread(task);
backgroundThread.setDaemon(true);
backgroundThread.start();
}
private void runTask() {
for (int i = 1; i <= 10; i++) {
try {
String status = "Processing " + i + " of " + 10;
//Add the new change as a Runnable here
//It will be run in the in the next update of the UI
tasks.add(()->{
statusLabel.setText(status);
textArea.appendText(status + "\n");
});
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
NOTE
It still depends for what purpose you are going to use it.
Those are just two approaches.
When you only update declared values, you could also use Property Bindings.
(StringProperty in your case)
And I would personally always go with the AnimationTimer.
RunLater decides when it is "fine" with queuing the Runnable. With the Animationtimer, you can be sure it is executed each tick, when a update is needed.
You could also Store all the updates and then do a "poll" of the new Values each tick.
TASK
When you know exactly where you want to put which value you should use Task and bind it the the UI element, that would also work.
Label statusLabel = new Label("Not Started...");
Task<String> t = new Task<String>() {
#Override
protected String call() throws Exception {
int i = 0;
while(//any condition ... ) {
i++;
Thread.sleep(1000);
updateValue("i is " + i);
}
return "last value!";
}
// now Bind the TextProperty of the Label to the ValueProperty of the
// Task
statusLabel.getTextproperty().bind(task.getValueProperty());
Thread backgroundThread = new Thread(task);
backgroundThread.start();

Call variable outside a method javafx

I made a progressbar and it works perfectly. The only issue is that i want to show the progress percentage. first some background information: Netwerk reads in a large file(=bestand) line by line and everytime it reads a line a counter goes up by 1. So the percentage i want to get is the (counter/1380)*100 with 1380 is the number of lines the scanner reads in. When I run it Netwerk reads in the file while the progressbar is running and the progressbar stops when the scanner is ready (so this works perfectly). But the percentage doesn't upgrade. When I debugged I discovered that updateprogress is called after Netwerk has finished reading and therefore the percentage stays at -100%. Does someone have a solution for this?
Task<Integer> task = new Task<Integer>() {
#Override
public Integer call() throws Exception {
n = new Netwerk(bestand);
updateProgress(((n.getTeller()/1380)*100), 100);
return null;
}
};
pb = new ProgressBar();
pb.progressProperty().bind(task.progressProperty());
Label lg = new Label();
lg.textProperty().bind(Bindings.format("Even geduld... %.0f%%",
task.progressProperty().multiply(100)));
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
progressStage.close();
SaveStage = new Stage();
//initStage
}
});
new Thread(task).start();
So what happens is that I have a thread with a task in it, the task;
1. makes a Netwerk that reads in the document and increments the counter( i tested the counter and it works)
2. Updateprogress() to update the counter has well
then i bind the task to the progressbar and i bind the label with a textProperty. The problem is that the label doesn't update.
Define a variable as attribute of your class
class YourClass {
private volatile int percentage;
Initialize it when you start your ProgressBar, and assign it inside Task:
Task task = new Task<Integer>() {
#Override
public Integer call() throws Exception {
n = new Netwerk(bestand);
// assign here the percentage variable
percentage = n.getPercentage();
if (n.getTeller() == 1380) {
return null;
}
return null;
}

Display message during loading

I have a JavaFX with TabPane which holds Java Objects with data into different tabs. I found that when the content of the tab takes time to load because there are SQL queries for execution the application just hangs. Is there any way to display some "Loading" message during the content utilization? for example:
Tab.setContent(<some_heavy_Java_Object>);
Is there any workaround to solve this in JavaFX or Java?
P.S I tested this code sample but I get error when I try to run the code:
TabContentInfrastructure content;
class GetDailySalesService extends Service<ObservableList<Object>>
{
#Override
protected Task createTask()
{
return new GetDailySalesTask();
}
}
class GetDailySalesTask extends Task<ObservableList<Object>>
{
#Override
protected ObservableList<Object> call() throws Exception
{
content = new TabContentInfrastructure();
return (ObservableList<Object>) content.initTestTabContentData();
}
}
..........
VBox vbox = new VBox();
content = new TabContentInfrastructure();
vbox.getChildren().add(content.initTestTabContentData());
GetDailySalesService service = new GetDailySalesService();
Region veil = new Region();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
veil.setPrefSize(240, 160);
ProgressIndicator p = new ProgressIndicator();
p.setMaxSize(140, 140);
p.progressProperty().bind(service.progressProperty());
veil.visibleProperty().bind(service.runningProperty());
p.visibleProperty().bind(service.runningProperty());
//tableView.itemsProperty().bind(service.valueProperty());
StackPane stack = new StackPane();
stack.getChildren().addAll(vbox, veil, p);
service.start();
tabdata.setContent(stack);
Can you help me to solve this issue.
Another attempt to solve the issue:
Task<VBox> task = new Task<VBox>()
{
#Override
protected VBox call() throws Exception
{
TabContentInfrastructure content = new TabContentInfrastructure();
return content.initTestTabContentData();
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
Region veil = new Region();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
veil.setPrefSize(240, 160);
ProgressIndicator p = new ProgressIndicator();
p.setMaxSize(140, 140);
//p.progressProperty().bind(service.progressProperty());
veil.visibleProperty().bind(task.runningProperty());
p.visibleProperty().bind(task.runningProperty());
//vb.visibleProperty().bind(service.runningProperty().not());
//tableView.itemsProperty().bind(service.valueProperty());
StackPane stack = new StackPane();
task.setOnSucceeded(new EventHandler<WorkerStateEvent>()
{
#Override
public void handle(WorkerStateEvent t){
System.out.print("Entered setOnSucceeded**********" + t.getSource().getValue());
stack.getChildren().clear();
stack.getChildren().addAll(task.getValue());
}
});
stack.getChildren().addAll(veil, p);
tabdata.setContent(stack);
This time the result is null.
And another unsuccessful attempt.
StackPane stack = new StackPane();
Region veil = new Region();
ProgressIndicator p = new ProgressIndicator();
Task<VBox> task = new Task<VBox>()
{ // create new task
#Override
public VBox call() throws InterruptedException
{
Platform.runLater(new Runnable()
{ // USE THIS INSTEAD
#Override
public void run()
{
try
{
// ui updates here(inside application thread)
// this is needed if you want to update your ui
// you cannot update any ui from outside the application thread
TabContentInfrastructure content = new TabContentInfrastructure();
//stack.getChildren().clear();
stack.getChildren().addAll(content.initTestTabContentData());
}
catch (InterruptedException ex)
{
//Logger.getLogger(InfrastructureDataTabs.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
return null;
}
};
new Thread(task).start();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
veil.setPrefSize(240, 160);
p.setMaxSize(140, 140);
p.progressProperty().bind(task.progressProperty());
veil.visibleProperty().bind(task.runningProperty());
p.visibleProperty().bind(task.runningProperty());
//vb.visibleProperty().bind(service.runningProperty().not());
//tableView.itemsProperty().bind(service.valueProperty());
stack.getChildren().addAll(veil, p);
tabdata.setContent(stack);
you must load the data in a different Task Thread, I see that you are trying to do the same. The problem with your code is that you are not updating your progress bar. You must use updateProgress as shown here
http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm#BABGJIDB
Here is a very nice example from Jewelsea where he has very nicely displayed the use of Task and how to use it to update the progress on the UI
Update progress bar and multiple labels from thread
Here you can find out how to use the Task as well as update the UI from the task
Some more Nice examples are
https://community.oracle.com/message/9927179#9927179
https://community.oracle.com/message/10631701#10631701
You should just execute the expensive computations in another thread and then update e.g. a progresss bar in the javafx application thread.
Also your application wont hang during the process anymore.
Like this:
Task task = new Task<Void>() { // create new task
#Override
public Void call() {
// do expensive computations here
Platform.runLater(new Runnable() { // return to application thread
#Override
public void run() {
// ui updates here(inside application thread)
}
});
return null;
}
};
new Thread(task).start(); // execute task in new thread
Hope it helps, Laurenz.
EDIT -------------
Task<Void> task = new Task<Void>() { // create new task
#Override
public Void call() {
try {
Thread.sleep(50); // this simulates expensive computations(in your case loading) - your app would hang for this duration
} catch (InterruptedException e) {
e.printStackTrace();
}
// REMOVE THE SLEEP AND PUT YOUR TASK HERE
// Main.this.root.setPrefHeight(50); // would NOT work(because outside application thread)
Platform.runLater(new Runnable() { // USE THIS INSTEAD
#Override
public void run() {
// ui updates here(inside application thread)
// this is needed if you want to update your ui
// you cannot update any ui from outside the application thread
}
});
return null;
}
};
new Thread(task).start(); // execute task in new thread

IllegalStateException while creating a stage in another thread

I have a problem with opening one more stage in another thread. No exceptions appears if I'm opening this stage in the same thread.
void hashMapDeclaration(){
actions2methods.put("NEW", new Runnable() {#Override public void run() { newNetCreation(); }});
actions2methods.put("LOAD", new Runnable() {#Override public void run() { loadNetState(); }});
...... //other hashes
}
HBox buttonBuilder(double spacing,double layoutX,String... bNames){
HBox lBar = new HBox(10);
.... //some code
for(final String text : bNames){ //in my case text variable value is "NEW" so it should run method newNetCreation
Button newButton = new Button();
newButton.setText(text);
.... //code
newButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent paramT) {
Thread t;
EventQueue.isDispatchThread();
t = new Thread(actions2methods.get(text));
t.start(); // Start the thread
System.out.println("button pressed");
}
});
lBar.getChildren().add(newButton);
}
return lBar;
}
void newNetCreation(){
final Stage dialogStage = new Stage();
final TextField textField;
dialogStage.initOwner(stage);
dialogStage.initModality(Modality.WINDOW_MODAL);
dialogStage.setFullScreen(false);
dialogStage.setResizable(false);
dialogStage.setScene(SceneBuilder
.create()
.fill(Color.web("#dddddd"))
.root(textField = TextFieldBuilder
.create()
.promptText("Enter user name")
.prefColumnCount(16)
.build()
)
.build()
);
textField.textProperty().addListener(new ChangeListener() {
public void changed(ObservableValue ov, Object oldValue, Object newValue) {
System.out.println("TextField text is: " + textField.getText());
}
});
dialogStage.show();
System.out.println("new net");
}
Method newNetCreation is the one that cause the problem. All actions in my program are store in a HashMap. Method buttonBuilder creates the new thread and should launch methods according to variable value and in my case he must call newNetCreation method, but when he tries, the following exception occurs:
Exception in thread "Thread-3" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Unknown Source)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(Unknown Source)
at javafx.stage.Stage.<init>(Unknown Source)
at javafx.stage.Stage.<init>(Unknown Source)
at projavafx.starterapp.ui.StarterAppMain.newNetCreation(StarterAppMain.java:400)
at projavafx.starterapp.ui.StarterAppMain$7.run(StarterAppMain.java:354)
at java.lang.Thread.run(Thread.java:722)
All UI operation for JavaFX should be perfromed on FX application thread.
Here is your code:
Thread t;
t = new Thread(actions2methods.get(text));
t.start(); // Start the thread
t is thread which you runs your method on. It's obviously not FX thread as stated in log your provided: java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3
If you want to run Runnable on FX thread, use next code:
Platform.runLater(actions2methods.get(text));

Using threads in a Java FX UI that should return a result

I am probably missing something here, but I'll try and explain what I want to achieve and then someone please tell me that I am doing it wrong(which I am :) ) and point me in the right direction?
I am using JavaFX 2.0, but I think this problem would lend itself to Swing, or any UI framework.
I want to develope a simple splash screen for my application, when the splash screen starts, I want to have a message label that will be used to update a user on whats happening, in regards to configuring up the back end of the application. My application start up has 2 steps, the first step uses Spring to initialise the Application Context, which in turn initialises the DB (JPA 2.0/Hibernate/etc). The second part of my application start up process will query the DB for the initial data which will be used to populate the UI. Both these steps need to be complete before I can close the splash screen, and between each step I want to update the label in the splash screen to let a user know which stage is being done at that time.
I have broken this down into the following simple program which uses JavaFX and a button, when the button is pressed a new thread is created, that starts another class, which just performs some count to an abitary value, and then another another thread is created to simlate the second step of the start up process, But my issue is here that the second thread attempts to run before the first thread has finished, and as a result runs into a NPE.
Below is a breakdown of some simple code that highlights this issue:
public class Test extends Application
{
private LongTester lt;
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage)
{
Button btn = new Button();
final Label lblText = new Label("Starting");
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
new Thread(new ConstructorRunnable()).start();
lblText.setText("More Loading?");
new Thread(new MethodRunnable()).start();
lblText.setText("Finished");
}
});
HBox root = new HBox();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
private class ConstructorRunnable implements Runnable
{
#Override
public void run()
{
lt = new LongTester();
}
}
private class MethodRunnable implements Runnable
{
#Override
public void run()
{
lt.countAgain();
}
}
private class LongTester
{
public LongTester()
{
for (int idx = 0; idx < 1000000; idx++)
{
System.out.println("Constructor: " + idx);
}
}
public Boolean countAgain()
{
for (int idx = 0; idx < 1000000; idx++)
{
System.out.println("Method: " + idx);
}
return Boolean.TRUE;
}
}
}
Can anyone point out my mistake?
I'd advise using a Task to execute your startup tasks and message progress back to your splash screen (similar to the approach in this sample created for a prior stackoverflow question on splash screens). If you want stuff in your task to run sequentially, just use one thread for the task rather than two.
Sample code for your task might look something like:
final Task<Data> initTask = new Task() {
#Override protected Data call() throws InterruptedException {
updateMessage("Initializing Application");
MyApp.initializeAppContext();
updateMessage("Loading Data");
Data data = DB.loadData();
updateMessage("Data Loaded");
return data;
}
showSplash(initStage, initTask);
new Thread(initTask).start();
showMainStage(initTask.valueProperty());
To fix your problem, you can use a CountDownLatch
This can be used the following way:
private class ConstructorRunnable implements Runnable {
CountDownLatch gate_ = null;
public ConstructorRunnable(CountDownLatch gate){
gate_ = gate;
}
#Override
public void run() {
lt = new LongTester();
gate_.countDown(); // Signal the second thread to start
}
}
private class MethodRunnable implements Runnable{
CountDownLatch gate_ = null;
public MethodRunnable(CountDownLatch gate){
gate_ = gate;
}
#Override
public void run(){
CountDownLatch.await(); // Wait for the first thread to finish
lt.countAgain();
}
}
This can now be used like this:
CountDownLatch gate = new CountDownLatch(1);
new Thread(new ConstructorRunnable(gate)).start();
lblText.setText("More Loading?");
new Thread(new MethodRunnable(gate)).start();
lblText.setText("Finished");
On a side note: as your tasks are sequential, why is there a need for having several threads? Threads are made to run multiple tasks running in parallel and your case does not make a use case for threads as these operations are sequential

Categories

Resources