My application contains a ListView
that kicks off a background task every time an item is selected. The background task then updates information on the UI when it completes successfully.
However, when the user quickly clicks one item after another, all these tasks continue and the last task to complete "wins" and updates the UI, regardless of which item was selected last.
What I need is to somehow ensure this task only has one instance of it running at any given time, so cancel all prior tasks before starting the new one.
Here is an MCVE that demonstrates the issue:
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class taskRace extends Application {
private final ListView<String> listView = new ListView<>();
private final Label label = new Label("Nothing selected");
private String labelValue;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
// Simple UI
VBox root = new VBox(5);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
root.getChildren().addAll(listView, label);
// Populate the ListView
listView.getItems().addAll(
"One", "Two", "Three", "Four", "Five"
);
// Add listener to the ListView to start the task whenever an item is selected
listView.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, newValue) -> {
if (newValue != null) {
// Create the background task
Task task = new Task() {
@Override
protected Object call() throws Exception {
String selectedItem = listView.getSelectionModel().getSelectedItem();
// Do long-running task (takes random time)
long waitTime = (long)(Math.random() * 15000);
System.out.println("Waiting " + waitTime);
Thread.sleep(waitTime);
labelValue = "You have selected item: " + selectedItem ;
return null;
}
};
// Update the label when the task is completed
task.setOnSucceeded(event ->{
label.setText(labelValue);
});
new Thread(task).start();
}
});
stage.setScene(new Scene(root));
stage.show();
}
}
When clicking on several items in random order, the outcome is unpredictable. What I need is for the label to be updated to show the results from the last Task
that was executed.
Do I need to somehow schedule the tasks or add them to a service in order to cancel all prior tasks?
EDIT:
In my real world application, the user selects an item from the ListView
and the background task reads a database ( a complex SELECT
statement) to get all the information associated with that item. Those details are then displayed in the application.
The issue that is happening is when the user selects an item but changes their selection, the data returned displayed in the application could be for the first item selected, even though a completely different item is now chosen.
Any data returned from the first (ie: unwanted) selection can be discarded entirely.