JavaFX uses observable lists for their chart series. I want to load the data in the series from a different thread, and I have done so by using Task and Platform.runLater. However, the UI is still not very responsive and the data does not look smooth coming in.
I would like the following to happen:
The UI to remain responsive.
The data loads in the graph smoothly - one at a time in each series.
Order of operations:
Load the page
Load the empty chart series in the graph
Press the generate button
Load data into each series
Relevant part of the code:
Loads series into chart
private static ObservableList<XYChart.Series<Number, Number>> pathSeries = FXCollections.observableArrayList();
final LineChart<Number,Number> pathChart =
new LineChart<Number,Number>(xAxis,yAxis);
for (int n=0; n<proc.getNumPath(); n++)
{
pathSeries.add(new Series<Number, Number>());
}
pathChart.getData().addAll(pathSeries);
Loads data to series
Task task = new Task<Void>() {
#Override public Void call() {
// PATH SERIES
for (int n=0; n<proc.getNumPath(); n++)
{
for(int i=0; i<=proc.getM(); i++)
{
int pathNumber = n;
int dataIndex = i;
Platform.runLater(new Runnable() {
#Override
public void run() {
pathSeries.get(pathNumber).getData().add(new XYChart.Data<Number, Number>(proc.getT()[dataIndex],
proc.getX()[pathNumber][dataIndex]));
}
});
if (isCancelled()) {
updateMessage("Cancelled");
break;
}
}
}
return null;
}
};
#FXML
public void initialize(){
generateButton.setOnAction(e -> {
pathWorker = new Thread(task);
pathWorker.setDaemon(true);
pathWorker.start();
});
}
How can I improve my code without getting too complicated?
Use this inside the for loop.
Thread.sleep(100);
Related
I'm looking for a suitable way to display the processing time of parallel running Tasks on a separate stage.
I want to execute different tasks combined in an ArrayList - one after the other. For this case I'm using a ThreadPool. After each executed list, I want to wait until all tasks are completed. Only when the tasks have reached the status „succeeded“, I want to do something in the MainThread. After that I want to execute another list of tasks and visualize them on a separate stage as well. The following figure shows the desired processing sequence (depending on the source code listed below):
enter image description here
For this purpose I have written the classes MyLoader. The MyLoader-class contains a separate Task and binds the progress-properties with a Label and a Progressbar in the constructor:
public class MyLoader {
public Label label = null;
public ProgressBar progressBar = null;
public VBox vbox;
public Task<Integer> task = null;
public String name;
public MyLoader(String name) {
this.name = name;
this.label = new Label();
this.progressBar = new ProgressBar();
this.vbox = new VBox(2);
//UI-Layout for Progress
this.vbox.getChildren().addAll(this.label, this.progressBar);
HBox.setHgrow(this.vbox, Priority.ALWAYS);
this.vbox.setAlignment(Pos.CENTER);
this.progressBar.prefWidthProperty().bind(this.vbox.widthProperty().subtract(20));
//Counter-Size
Random r = new Random();
int max = r.nextInt((100 - 50) + 1) + 50;
//Task
this.task = new Task<Integer>() {
#Override
protected Integer call() throws Exception {
int idx = 0;
while(idx <= max) {
Thread.sleep(20); //... for long lasting processes
updateMessage(name+"-progress: "+idx);
updateProgress(idx, max);
idx++;
}
return max;
}
protected void succeeded() {
updateMessage(name+" succeeded!");
System.out.println(name+" succeeded!");
super.succeeded();
}
};
//Bind Properties
this.label.textProperty().bind(task.messageProperty());
this.progressBar.progressProperty().bind(task.progressProperty());
}
}
In the MainClass, I combine several MyLoader instances in an ArrayList and run them with an ExecutorService. To create the new stage I use the static method progressStage(List). Each Stage is shown before the ExecutorService executes the respective tasks. Here's the MainClass code:
public class MainClass extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
//Thread-Pool
ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//FirstLoaders
List<MyLoader> firstLoaders = new ArrayList<MyLoader>();
firstLoaders.add(new MyLoader("A"));
firstLoaders.add(new MyLoader("B"));
//Show 1. Stage
Stage firstStage = progressStage(firstLoaders);
firstStage.show();
//Execute firstLoaders
for(MyLoader l1 : firstLoaders)
es.execute(l1.task);
//1) TODO: How can I wait for the completion of the first loaders and start the second loaders?
//... doSomething1() ...
//SecondLoaders
List<MyLoader> secondLoaders = new ArrayList<MyLoader>();
secondLoaders.add(new MyLoader("C"));
secondLoaders.add(new MyLoader("D"));
secondLoaders.add(new MyLoader("E"));
//Show 2. Stage
Stage secondStage = progressStage(secondLoaders);
secondStage.setX(firstStage.getX());
secondStage.setY(firstStage.getY()+firstStage.getHeight());
secondStage.show();
for(MyLoader l2 : secondLoaders)
es.execute(l2.task);
//2) TODO How can I wait for the completion of the second loaders and start the primaryStage?
//... doSomething2() ...
Scene scene = new Scene(new StackPane(), 450, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
static Stage progressStage(List<MyLoader> loaderTasks) {
int count = loaderTasks.size();
VBox loadBox = new VBox(count);
for(int i=0; i<count; i++)
loadBox.getChildren().add(loaderTasks.get(i).vbox);
HBox.setHgrow(loadBox, Priority.ALWAYS);
loadBox.setAlignment(Pos.CENTER);
Stage dialogStage = new Stage();
dialogStage.setScene(new Scene(loadBox, 300, count * 50));
dialogStage.setAlwaysOnTop(true);
return dialogStage;
}
}
The program is executable so far - but the calculation sequence appears completely parallel.
What I tasted:
1) So far I have managed to get the process to be read and stopped using the get() method. But then the stage is only displayed when the threads in the background have finished their work.
//1) TODO: „doSomeThing1()“
List<Integer> integers = new ArrayList<Integer>();
for(MyLoader ml : firstLoaders)
integers.add(ml.task.get());
System.out.println(integers.toString());
2) Also with the Task.setOnSucceded() method I could not get any useful results yet. Mainly because the stage is only shown after the computing. The problem is that I am not able to query the status of all tasks at a defined time.
3) The application of a CountDownLatch has also achieved a comparable result.
4) In addition, the shutdown() method of the ExecutorService causes a termination. This solution is therefore also not suitable.
//1) TODO: „doSomeThing1()“
es.shutdown();
try {
es.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
//SecondLoaders
//...
}catch (InterruptedException e) {
e.printStackTrace();
}
Is there a suitable approach for such intentions? So far I have not come to any useful result.
When a task is done, just update a counter and check, if the task currently completed was the last one in the current set.
The following code demonstrates this. (There are certainly things in the code that could be improved though, but the concept should get clear.)
public class App extends Application {
public static void main(String[] args) {
launch(args);
}
private VBox taskViewContainer;
ExecutorService executor;
int tasksDone;
private void runTasks(List<MyTask> tasks, IntegerProperty index) {
if (tasks.isEmpty()) {
index.set(index.get()+1);
} else {
int taskCount = tasks.size();
tasksDone = 0;
for (MyTask task : tasks) {
taskViewContainer.getChildren().add(new TaskView(task));
task.setOnSucceeded(evt -> {
++tasksDone;
if (tasksDone == taskCount) {
// proceed to next task set after all tasks are done
index.set(index.get() + 1);
}
});
executor.submit(task);
}
}
}
#Override
public void init() throws Exception {
// create executor during initialisation
executor = Executors.newFixedThreadPool(4);
}
#Override
public void stop() throws Exception {
// shutdown executor when javafx shuts down
executor.shutdownNow();
}
#Override
public void start(Stage primaryStage) throws Exception {
taskViewContainer = new VBox();
Label text = new Label();
// generate random set of tasks
Random random = new Random();
List<List<MyTask>> taskLists = new ArrayList<>();
for (int i = 0; i < 20; ++i) {
int count = random.nextInt(10) + 1;
List<MyTask> tasks = new ArrayList<>(count);
taskLists.add(tasks);
for (int j = 0; j < count; ++j) {
tasks.add(new MyTask(String.format("%d.%c", i+1, (char) ('A'+j)), random.nextInt((100 - 50) + 1) + 50));
}
}
// property holding the current index in the task set list
IntegerProperty index = new SimpleIntegerProperty(-1);
index.addListener((o, oldValue, newValue) -> {
// gui update for change of task set
taskViewContainer.getChildren().clear();
text.setText(String.format("Task set %d / %d done", newValue, taskLists.size()));
int i = newValue.intValue();
if (i < taskLists.size()) {
// launch next set of tasks
runTasks(taskLists.get(i), index);
}
});
// start initial tasks
index.set(0);
text.setMinWidth(200);
text.setMaxWidth(Double.MAX_VALUE);
HBox root = new HBox(text, taskViewContainer);
root.setMinHeight(10 * 50);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
class TaskView extends HBox {
TaskView(MyTask task) {
setPrefSize(400, 50);
ProgressBar progress = new ProgressBar();
progress.progressProperty().bind(task.progressProperty());
Label label = new Label(task.getName());
Label message = new Label();
message.textProperty().bind(task.messageProperty());
getChildren().addAll(progress, new VBox(label, message));
}
}
class MyTask extends Task<Integer> {
private final int max;
private final String name;
public String getName() {
return name;
}
public MyTask(String name, int max) {
this.max = max;
this.name = name;
}
#Override
protected Integer call() throws Exception {
int idx = 0;
while(idx <= max) {
Thread.sleep(20); //... for long lasting processes
updateMessage(name+"-progress: "+idx);
updateProgress(idx, max);
idx++;
}
return max;
}
}
The above code does not take the possibility of canceling tasks/tasks terminating with an exception.
I have a problem how to correctly update Java FX UI. I'm moving from Swing to FX for the first time and also ExecutorService. Problem is I need to show gif file and update progress bar during code execution but instead I get white screen with mouse loading icon. The gif and progressbar are eventually showed but after finishing this for cycle: for (int i = 0; i < futures.size(); i++)
I thought that running tasks in ExecutorService is in separate thread and using Platform.runLater for progressbar will separate UI from long running code in ExecutorService. Can you give me some explanation what is going on please?
Controller.java:
public void initialize() {
ivGif.setImage(new Image(Main.class.getResourceAsStream("/test/loading.gif")));
}
public void synchronizeFiles() {
Platform.runLater(() -> pbDownloading.setVisible(true));
ExecutorService pool = Executors.newFixedThreadPool(1);
ArrayList<Future<Boolean>> futures = new ArrayList<>();
File localFile = new File(simplified code here);
Future<Boolean> f = pool.submit(new DownloadTask(new URL(simplified code here), localFile));
futures.add(f);
for (int i = 0; i < futures.size(); i++) {
final int position = i;
Platform.runLater(() -> {
pbDownloading.setProgress(position / (double) futures.size());
});
if (!futures.get(i).get(600, TimeUnit.SECONDS)) {
System.out.println("ShutdownNow");
pool.shutdownNow();
}
}
}
DownloadTask.java:
public class DownloadTask implements Callable<Boolean> {
protected Category cat = Category.getInstance(DownloadTask.class.getName());
private URL fileURL;
private File toPath;
public DownloadTask(URL fileURL, File toPath) {
this.fileURL = fileURL;
this.toPath = toPath;
}
private void downloadFile(URL fileURL, File toPath) throws IOException {
ReadableByteChannel readableByteChannel = Channels.newChannel(fileURL.openStream());
if (!toPath.getParentFile().exists()) {
if (!toPath.getParentFile().mkdirs()) throw new IOException("Unable to create parent dirs for file: "+toPath.getAbsolutePath());
}
FileOutputStream fileOutputStream = new FileOutputStream(toPath);
fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
URLConnection urlConnection = fileURL.openConnection();
if (!toPath.setLastModified(urlConnection.getLastModified())) cat.error("Unable to write modified time stamp for file: "+toPath.getAbsolutePath());
}
#Override
public Boolean call() {
try {
downloadFile(fileURL, toPath);
} catch (IOException e){
System.out.println(e);
cat.error(e, e);
return false;
}
return true;
}
}
Thank you
EDIT: I didn't realize that the main class is running from FX Application Thread. So I had reverse threads and just applied the new thread (() -> {...}).Start(); in the main class in the start method and it's ok.
I am populating a comboBox with items taken from a sql database when initializing the thread.
If anyone's interested:
public void initialize(URL arg0, ResourceBundle arg1) {
if(arr != null || arr.length > 0) {
for(int i = 0; i<arr.length; i++) {
cmBox.getItems().add(arr[i]);
}
}
}
I have a part of my code that adds a new value to the sql table, and I want to re-populate the comboBox when that happens.
if I do:
cmBox.getItems().clear();
arr = sqld.selectAll();
if(arr != null || arr.length > 0) {
for(int i = 0; i<arr.length; i++) {
cmBox.getItems().add(arr[i]);
}
}
It works fine, but I can't see the new changes unless I close and re-open the window that displays the comboBox.
No errors or anything, just looking for creative ways of re-loading a comboBox and have it actually show the updated values without manually closing and re-opening the window.
You don't need a refresh button just run comboBox.setItems(...) when you add "a new value to the sql table" this should update the combo box here is an example
public class Main extends Application {
private int[] data;
private int dataCount = 0;
#Override
public void start(Stage primaryStage) throws Exception{
data = randomizeData(dataCount);
ComboBox comboBox = new ComboBox();
comboBox.setItems(FXCollections.observableArrayList(
Arrays.stream(data).boxed().collect(Collectors.toList())));
Button updateDataButton = new Button("Update values in SQL Table");
updateDataButton.setOnAction(event -> {
//Update your SQL data
updateData();
//Refresh List
comboBox.setItems(FXCollections.observableArrayList(
Arrays.stream(data).boxed().collect(Collectors.toList())));
});
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
vBox.getChildren().addAll(comboBox, updateDataButton);
Scene scene = new Scene(vBox);
primaryStage.setScene(scene);
primaryStage.show();
}
private int[] randomizeData(int additional){
int[] data = new int[5+additional];
for (int i = 0; i < data.length; i++) {
data[i] = (int) (Math.random()*10);
}
return data;
}
private void updateData(){
data = randomizeData(++dataCount);
}
public static void main(String[] args) { launch(args); }
}
apologies for the length of my code. I realized last night that I was on the wrong path and now have gotten stuck on an issue that I think relates to JavaFX event handling. Initially I had the logic functioning outside a GUI in a basic loop that depended on interaction through the console. Everything was working great. I've now tried to get this to work in a GUI with interaction from the user.
I have two main problems with the code below.
The first is that the text in textArea is not updating with additional text after the startButton executes the start of my main logic sequence. The first append starts right under the first while loop. I was hoping to have this show up in the GUI as the logic executes. I'm not sure if I need to tell the GUI to update at certain intervals or if there's something else wrong.
Second, I'm not sure how to get the program to wait for the user to type in something into textField before hitting the textButton I created to continue on. I used to have a scanner created which caused the program to wait in the console for input. I realize I need some way of telling it to wait for a button press when it's running inside JavaFX.
I chose not to include the rest of the code to make things easier to read, but I can add it on if it will help resolve this issue.
Thank you everyone for your help!
public class FxApp extends Application {
//Creates FileParser object with methods that alter the incoming Array of Strings into the format we need
FileParser fileParser = new FileParser();
Configure configure = new Configure();
private String text;
private String initialState;
private ArrayList<Machine> machines = new ArrayList<Machine>();
private Map<String, String> initialStates = new HashMap<String, String>();
private Map<String, String> states = new HashMap<String, String>();
private Map<String, ArrayDeque<String>> queues = new HashMap<String, ArrayDeque<String>>();
private Map<Integer, ArrayList<String>> parsedData = new HashMap<Integer, ArrayList<String>>();
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("File Chooser");
FileChooser fileChooser = new FileChooser();
fileChooser.getExtensionFilters().addAll(new ExtensionFilter("Text Files", "*.txt"));
Button startButton = new Button("Start");
Button openButton = new Button("Click to open a file...");
openButton.setPrefSize(200, 80);
Button textButton = new Button("Enter");
TextArea textArea = new TextArea();
textArea.setWrapText(true);
TextField textField = new TextField();
Label lbl = new Label();
VBox vbox = new VBox(lbl, openButton, startButton, textArea, textField, textButton);
vbox.setSpacing(10);
vbox.setPadding(new Insets(15));
lbl.setText("This tool creates virtual automata based \ron the file.");
Scene scene = new Scene(vbox, 640, 480);
primaryStage.setScene(scene);
primaryStage.show();
openButton.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
File file = fileChooser.showOpenDialog(primaryStage);
if (file != null) {
//Execute the method to convert to string array before sending to file parser
try {
fileParser.convertFile(file);
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
});
textButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
text = textField.getText();
}
});
startButton.setOnAction(new EventHandler <ActionEvent>()
{
public void handle(ActionEvent event)
{
machineCreation();
String exit = "no";
String nextLine = null;
ArrayList<String> listOfCurrentTransitions = new ArrayList<String>();
int nextInt = 0;
states = initialStates;
while(!(exit.toLowerCase().equals("yes"))) {
textArea.appendText("Choose a state to load");
//Print out the states possible for each machine
ArrayList<String> tempTrans = machines.get(nextInt).getTransitions();
//This loops through the list of transitions of the machine and pulls possible transitions from its current state
for(int i = 0; i < tempTrans.size(); i++) {
String pull = tempTrans.get(i);
String[] apart = pull.split(" ");
pull = apart[0];
if(states.get(Integer.toString(nextInt)).equals(pull)) {
listOfCurrentTransitions.add(tempTrans.get(i));
}
}
if(!(listOfCurrentTransitions.isEmpty())) {
textArea.appendText("The following transitions are possible. Choose one: " + listOfCurrentTransitions);
}
else {
textArea.appendText("No transitions for this machine exist from its current state");
}
//Tell GUI to wait for user input in textField and execute textButton which assigns to String text. Resume on button click.
The while loop blocks the JavaFX application thread which prevents updates of the GUI and handling of events.
You need to execute the logic of a single iteration of the loop on each "text commit" instead:
private TextArea textArea;
private void activateState(int nextInt) {
ArrayList<String> listOfCurrentTransitions = new ArrayList<String>();
textArea.appendText("Choose a state to load");
//Print out the states possible for each machine
ArrayList<String> tempTrans = machines.get(nextInt).getTransitions();
//This loops through the list of transitions of the machine and pulls possible transitions from its current state
for(int i = 0; i < tempTrans.size(); i++) {
String pull = tempTrans.get(i);
String[] apart = pull.split(" ");
pull = apart[0];
if(states.get(Integer.toString(nextInt)).equals(pull)) {
listOfCurrentTransitions.add(tempTrans.get(i));
}
}
if(listOfCurrentTransitions.isEmpty()) {
textArea.appendText("No transitions for this machine exist from its current state");
} else {
textArea.appendText("The following transitions are possible. Choose one: " + listOfCurrentTransitions);
}
}
#Override
public void start(Stage primaryStage) throws Exception {
...
textArea = new TextArea();
...
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
machineCreation();
activateState(0);
}
});
textButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// read input and ask for more input...
int nextState = Integer.parseInt(textField.getText()); // TODO: deal with invalid input
activateState(nextState);
}
});
You probably need to fix the logic a bit to verify a transition is valid, change the values of some fields ect...
I want to run a task in background updating intermediate results in the view.I am trying to implement MVC JavaFX application. The task is defined in the Model.
I want to send to the main threath partial results in order to show them in the View.
I use updateValue() to do so. Also, I define object property and a listener in the controller.
My problem: The method changed() from the listener, is not being fired each time that updateValue() is executed in the Task. Why? How can I force it to do this?.
I have not found much complex examples.
What I have so far:
Model.cpp
ComplexObject _complexO;
public Task<ComplexObject> getModelTask() {
return new Task<ComplexObject>() {
#Override
protected ComplexObject call() throws Exception {
int numberOfFiles = 0;
boolean filesToRead = true;
while (filesToRead){
// ....
_complexO = new ComplexObject();
try{
//..
if(f.exists()){
_complexO.initialize();
numberOfScans ++;
}
else{
_complexO.initializeToNull();
}
String stringNumber = Converter.toString(numberOfFiles);
updateMessage(stringNumber);
updateValue(_complexO );
} catch(Exception ex){
ex.printStackTrace();
_complexO = null;
return _complexO;
}
filesToRead = areThereFilesToRead();
}
return _complexO;
}
};
}
Controller.cpp
...
Task< ComplexObject> task = _model.getModelTask();
_AJavaFXTextField.textProperty().bind(task.messageProperty());
_AJavaFXTextField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue observable, String oldValue, String newValue) {
System.out.println("Success with messageProperty!!" + newValue);
}
});
SimpleObjectProperty<ComplexObject> complexObjectProperty = new SimpleObjectProperty<>();
complexObjectProperty.bind(task.valueProperty());
complexObjectProperty.addListener(new ChangeListener<ComplexObject>(){
#Override
public void changed(ObservableValue<? extends ComplexObject> observable, ComplexObject oldValue, ComplexObject newValue) {
if(newValue.data == null ) {
System.out.println("value is new!!! " + scansNumber);
}
else if(newValue.isValid()){
System.out.println("I want to plot newValue data here");
}
}
});
Thread th= new Thread(task);
System.out.println("call TASK");
th.start();
}
My questions/conclusions here:
How to force to all times that I execute in the task updateValue() to really execute the listener - so execute the code where I want to plot data.
Why it is more times fire the bind for the messageProperty than the valueProperty? - it should be the same number of times.
Why I find that the code of the listener is fired more times when debug mode than normal execution?
Any recomendation of good sources about this topic (from a complex point of view) would be great.
I am looking from something in JavaFX to replace SwingWorker.
What I really whant at the end: To return a list of complexObjects from the task, and ideally, updateValue() would send the objects one per one (partial results)
I have followed:
https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html
Thanks very much for any contribuction
Task only guaranties that a value passes to updateValue or a value passed later will be set to the value property. This is done to increase performance of the application thread by limiting the number of changes the listeners are notified of.
Why it is more times fire the bind for the messageProperty than the valueProperty? - it should be the same number of times.
As described above there simply is no guaranty about the number of updates.
Why I find that the code of the listener is fired more times when debug mode than normal execution?
In general debugging makes your program smaller. The smaller the update frequency from the thread of your Task, the smaller the number of updates between the times the Task class updates the properties and the smaller the number of skipped. (The updates are probably executed every frame or every few frames.) If you even use a break-point/stepper in the task, you probably make the Task extremely slow while the application thread runs at normal speed.
It should be easy enough to implement publish on your own by using a List to buffer the updates
public abstract class JavaFXWorker<S, T> extends Task<S> {
private List<T> chunks = new ArrayList<>();
private final Object lock = new Object();
private boolean chunkUpdating = false;
protected final void publish(T... results) {
synchronized (lock) {
chunks.addAll(Arrays.asList(results));
if (!chunkUpdating) {
chunkUpdating = true;
Platform.runLater(() -> {
List<T> cs;
synchronized (lock) {
cs = chunks;
// create new list to not unnecessary lock worker thread
chunks = new ArrayList<>();
chunkUpdating = false;
}
try {
process(cs);
} catch (RuntimeException ex) {
}
});
}
}
}
protected void process(List<T> chunks) {
}
}
Sample use
#Override
public void start(Stage primaryStage) {
ListView<Integer> lv = new ListView<>();
Button btn = new Button("Run");
btn.setOnAction((ActionEvent event) -> {
JavaFXWorker<Void, Integer> worker = new JavaFXWorker<Void, Integer>() {
#Override
protected Void call() throws Exception {
final int maxCount = 100;
Random random = new Random();
int breakIndex = random.nextInt(maxCount-1)+1;
for (int i = 0; i < breakIndex; i++) {
publish(i);
}
// some break simulating a part long part of the task with no updates
Thread.sleep(3000);
for (int i = breakIndex; i <= maxCount; i++) {
publish(i);
}
return null;
}
#Override
protected void process(List<Integer> chunks) {
lv.getItems().addAll(chunks);
}
};
new Thread(worker).start();
});
Scene scene = new Scene(new VBox(btn, lv));
primaryStage.setScene(scene);
primaryStage.show();
}