Multithreading in JavaFX data binding int to Task value - java

I am fairly new to multi-threading and have run into a bit of difficulty getting tasks to work within my application.
I have a file browser that allows users to select file(s) to be imported and while this import is occurring I am trying to update a progress bar at the bottom of the screen. However, I am trying to avoid making the bar "indeterminate" for the import process but instead fill with the actual import progress.
In order to accomplish this, I have created 2 tasks, 1 to get a total count of the files to be imported from the selected items in the file browser tree and a 2nd to actually import the songs and update the progress bar based on a simple equation using the total count from first task.
However, I'm unsure of how to get the int value from the first task into the the second task.
This also brings up a few additional questions, first about just getting things to work and secondly about the performance aspect of my design.
Controller Class
#FXML
private void handleImportButtonAction(ActionEvent event) {
new Thread(getSelectedFilesLeafCountTask).start();
new Thread(importSongsTask).start();
}
Get Leaf Count Task
#Component
public class GetSelectedFilesLeafCountTask extends Task<Integer> {
#Autowired
private FileBrowserController fileBrowserController;
#Autowired
private FooterController footerController;
private Integer leafCount = 0;
#Override
protected Integer call() throws Exception {
Platform.runLater(new Runnable(){
footerController.getTaskProgressIndicator().setProgress(ProgressBar.INDETERMINITE);
footerController.getTaskLabel().setText("Indexing Files");
});
getSelectedItemsLeafCount(fileBrowserController.getFileBrowser().
getSelectionModel().getSelectedItems());
return leafCount;
}
// Recursively counts the leaves within a single or group of Files
private void getSelectedItemsLeafCount(List<TreeItem<File>> files) {
for(TreeItem<File> f : files) {
if(f.isLeaf()) {
leafCount++;
}else {
getSelectedItemsLeafCount(f.getChildren());
}
}
}
}
Import Songs Task
#Component
public class ImportSongsTask implements Runnable {
#Autowired
FileBrowserController fileBrowserController;
#Autowired
FooterController footerController;
#Autowired
ConsoleTabController consoleTabController;
#Autowired
DataModel datamodel;
private double totalFileCount; // This is the value needed from task1
private double completionCount;
private int importCount; // Total Number of Files that have been imported at the end of the task
private int failCount;
#Override
public void run() {
importCount = 0;
failCount = 0;
totalFileCount = fileBrowserController.getSelectedLeafCount();
Platform.runLater(new Runnable() {
#Override
public void run() {
footerController.getTaskLabel().setText(footerController.IMPORTING_FILES_MSG);
footerController.getTaskProgressIndicator().setProgress(0.0);
footerController.getTaskProgressIndicator().setVisible(true);
}
});
List<Song> importSongsList = new ArrayList<>();
importSongsList = importFilesForImportButton(fileBrowserController.getFileBrowser().getSelectionModel().getSelectedItems(), importSongsList);
datamodel.getWindowDisplaySongs().addAll(importSongsList);
Platform.runLater(new Runnable() {
#Override
public void run() {
// Reset button and progress bar values
completionCount = 0;
}
});
}
private List<Song> importFilesForImportButton(List<TreeItem<File>> files, List<Song> importList){
for(TreeItem<File> f : files) {
try {
if(f.isLeaf() && !datamodel.songWindowContainsFile(f.getValue())) {
// Perform Processing
Platform.runLater(new Runnable() {
#Override
public void run() {
footerController.getTaskProgressIndicator().setProgress((completionCount / totalFileCount)); // Need the value from task 1 here
}
});
completionCount++;
importCount++;
}else{
importFilesForImportButton(f.getChildren(), importList);
}
}catch(Exception e) {
}
}
return importList;
}
}
Additional Questions
Is there any better performing solution to allow me to have a progress bar to update based on the total file count that wouldn't require me to go through all of the files 2 times (once to get the total count for the progress bar, and then a second to actually import them)?
Is there a way for me to start the task to count the leaf items from within the import task but make sure that the import task doesn't continue until the counting task is 100% complete? because I should only need to start that task from within other background tasks but I am not sure how to make sure that it completes first.

Related

Fixing StackOverflowError recursivity using Runnable

I am getting StackOverflowError exception report while calling this recursive method :
private void downloadFiles(int index) {
if (index < totalFiles) {
downloadSingleFile(index, new DownloadCallback() {
#Override
public void onSuccess(String filePath) {
downloadFiles(index + 1);
}
});
}
}
I want to ask if I use a Runnable like this way:
int index = 0;
handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
downloadFiles();
}
};
handler.post(runnable);
private void downloadFiles() {
if (index < totalFiles) {
downloadSingleFile(index, new DownloadCallback() {
#Override
public void onSuccess(String filePath) {
index ++;
handler.post(runnable);
}
});
}
}
Will this be a recursivity as well and throw exception ?
Thanks
Your current use of recursion sort of defeats the purpose of using multiple threads. Currently, you only create a single thread which will enter downloadFiles(), and will then recursively try to download every file available. This is not really multithreading, it's single threading with recursion. There are several drawbacks to this approach. First, you are not taking advantage of the ability for multiple threads to do work in parallel. Second, since each subsequent recursive call is dependent on the previous one having succeeded, you are trying to download files in serial. If a given file download were to fail, it would break the rest of the recursive chain.
A better approach would be to spawn a new thread for each file download. This would allow you to use the power of multithreading to split the task in parallel, and it also allows progress to continue even if one thread were to encounter some problems.
Have a look at the following code snippet for an idea on how to approach your problem:
public class FileDownloader implements Runnable {
private index;
public FileDownloader(int index) {
this.index = index;
}
public void run() {
downloadSingleFile(index, new DownloadCallback() {
#Override
public void onSuccess(String filePath) {
// this may no longer be needed
}
});
}
}
// use a thread pool of size 5 to handle your file downloads
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int index=0; index < totalFiles; ++index) {
Runnable r = new FileDownloader(index);
executor.execute(r);
}
// shut down the thread pool executor and wait for it to terminate
executor.shutdown();
while (!executor.isTerminated()) {
}

Changelistener not triggered in Console but by GUI

I have a Task which should be runnable and updateable on Console and GUI. Consider my Task written as
public static Task<Void> doStuff() {
Task<Void> task;
task = new Task<Void>() {
final int totalSteps = 4;
#Override
public Void call() throws Exception {
updateProgress(0, totalSteps);
updateMessage("1");
action(1);
updateProgress(0, totalSteps);
updateMessage("2");
action(2);
//etc..
return null;
}
};
new Thread(task)
.start();
return task;
}
With bonding the Progress and Message Property within my JavaFX GUI, everything works as expected and the GUI gets updated according to the Progress.
Within my CLI, I tried to build a simple Progress Bar which updates the User about the Operation Progress
private static void progressBar(Task task) {
task.progressProperty().addListener((new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
// Return to line beginning
System.out.print("\r");
int percentage = (int) (100 * task.progressProperty().get());
System.out.format("[%3d%%] %s", percentage, task.messageProperty().get());
if (percentage == 100) {
System.out.println("Finished");
}
}
}));
}
}
As far as I could see with debugging, the Change Listeners changed Method will not get triggered. What did I set up wrong about it? Even the Finished Print at the End will not get written.
As stated in the comments, updates to the properties in a JavaFX Task are performed on the FX Application Thread. That thread is the thread used to render the JavaFX Scene graph and is started by the JavaFX toolkit when it is launched. If the JavaFX toolkit is not running, then the thread won't be started and those properties will not be updated.
If you want something that can be run in a background that needs to be functional outside of a JavaFX application, then you should use threading facilities that are not dependent on JavaFX. In this case you can simply implement Runnable or Callable. If you want to provide notifications when state changes, create a callback, to be called when the state changes. Callbacks are best represented using interfaces from the java.util.function package.
In the case you show, it looks like you only really need the message and progress together, so you could probably use a BiConsumer<String, Integer>.
public class DoStuff implements Runnable {
private final int totalSteps ;
private BiConsumer<String, Integer> update ;
public DoStuff(int totalSteps) {
this.totalSteps = totalSteps ;
}
public void setUpdate(BiConsumer<String Integer> update) {
this.update = update ;
}
#Override
public void run() {
if (update != null) {
update.accept("0", 0) ;
}
for (int i = 1 ; i <= totalSteps ; i++) {
action(i);
if (update != null) {
update.accept(Integer.toString(i), i);
}
}
}
private void action(int i) {
// ...
}
public int getTotalSteps() {
return totalSteps() ;
}
}
and now you would do
public void progressBar(DoStuff doStuff) {
doStuff.setUpdate((s, p) -> {
// Return to line beginning
System.out.print("\r");
int percentage = 100 * p / doStuff.getTotalSteps();
System.out.format("[%3d%%] %s", percentage, s);
if (percentage == 100) {
System.out.println("Finished");
}
});
}
And you execute this in a background thread with
new Thread(doStuff).start();
If you wanted to use this in a JavaFX environment, you can, but make sure your callback updates any UI components on the FX Application Thread:
DoStuff doStuff = ... ;
doStuff.setUpdate((s, p) -> Platform.runLater(() -> {
label.setText(s);
double progress = 1.0 * p / doStuff.getTotalSteps();
progressBar.setProgress(p);
}));
It's also reasonably easy to create a Task<Void> that wraps this, and exposes the progress and message in the usual JavaFX way:
DoStuff doStuff = ... ;
Task<Void> doStuffTask = new Task<Void>() {
#Override
protected Void call() {
doStuff.setUpdate((s, p) -> {
updateProgress(p, doStuff.getTotalSteps());
updateMessage(s);
});
doStuff.run();
return null ;
}
};
Then do
progressBar.progressProperty().bind(doStuffTask.progressProperty());
label.textProperty().bind(doStuffTask.messageProperty());
Thread t = new Thread(doStuffTask);
t.setDaemon(true);
t.start();
as usual.

JProgressBar doesn't update in real time within a loop [duplicate]

This question already has answers here:
Can a progress bar be used in a class outside main?
(3 answers)
Closed 7 years ago.
It is the first time I have to work with a progress bar and I'm facing a problem, besides I try to call its setValue(x) from everywhere it keeps on 0% and goes straight to 100% after my method routine finishes.
I tried to make an inner class that extends Thread, then after I tried to start a new Thread within my "main" method, then for the last I tried to use the Observer. These ones seems to have worked according to this posts but unfortunately not to me
Update JProgressBar from new Thread
Problem making a JProgressBar update values in Loop (Threaded)
please, could someone help me???
public class MainClass {
private void checkFiles() {
Task task = new Task();
task.start();
//here I have some Files validation...I don't think it is important to solve the progressbar problem
//so it will be ommited
//in this point I tried to call update to test the observer solution I found in another post here
//task.update(null, null);
JOptionPane.showMessageDialog(this, "Done!");
//here the bar jumps from 0% to 100%
}
private class Task extends Thread implements Observer {
public Task() {
}
//Dont bother with the calculum as I haven't finished working on them....
//The relevant thing here is that it starts a new Thread and I can see the progress
//increasing on console using system.out but my progress bar still don't change from 0%.
public void run() {
int maxSize = 100;
final int partsSize = maxSize / listaArquivosSelecionados.size();
while (listFilesValidated.size() != listFilesToValidate.size()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int progress = listFilesValidated.size() * partsSize;
System.out.println("Progress" + progress);
progressBar.setValue(progress);
}
});
try {
Thread.sleep(100);
}
catch (InterruptedException e) {}
}
}
//Just tried to set any value to check if it would update before the files validation thread finishes its work.
#Override
public void update(Observable arg0, Object arg1) {
progressBar.setValue(66);
}
}
You can create another class of ProgressBar (see Oracle tutorial) and use this:
ProgressBar pbFrame = new ProgressBar();
pbFrame.setVisible(true);
Executors.newSingleThreadExecutor().execute(new Runnable() {
#Override
public void run() {
// run background process
}
});
Or you can use SwingWorker, for example:
SwingWorker worker = new SwingWorker<MyReturnType, Void>() {
#Override
public MyReturnType doInBackground() {
// do your calculation and return the result. Change MyReturnType to whatever you need
}
#Override
public void done() {
// do stuff you want to do after calculation is done
}
};
I had the same question some years ago.

Setting up a progress bar in Java

I have a program currently and have tried to implement a progress bar with my code. Bellow is an example of the code currently. The main GUI is in its own class and instantiates other classes to then execute code within those classes' methods. An example is as follows:
class MainClass {
public javax.swing.JProgressBar progressBar;
private void combineActionPerformed(java.awt.event.ActionEvent evt) {
Combine combiner = new Combine();
combiner.Merge(folder);
}
}
It takes a folder listing and then goes to the Combine class which has the following code:
public class Combine extends SwingWorker<Integer,Integer>{
public void Merge(Folder []){ (for int i=0;i<folder.length;i++){
merge(folder[i]);
}
public void Merge(folder[]){
output stream;
}
}
How do I implement the swing worker properly in this example to make a progress update to the MainClass progress bar as each iteration of i occurs?
To begin, your worker is missing some methods it should implement, such as doInBackground() and done(). You also need a constructor to pass Folder[].
public class Combine extends SwingWorker<Integer,Integer>{
Folder[] folders;
public Combine (Folder[] folders)
{ this.folders = folders; }
private void Merge(Folder [])
{ (for int i=0;i<folder.length;i++)
{
merge(folder[i]);
//Send the message of progress here, it will be executed
//from doInBackground()
setProgress(....);
}
}
private void Merge(folder){
output stream;
}
protected Integer doInBackground()
{
merge(folders);
return null;
}
protected void done()
{ .... }
}
Then you would call this worker with
Combine combiner = new Combine(folders);
combiner.execute();
To track progress, this example is from SwingWorker API:
combiner.addPropertyChangeListener(
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer)evt.getNewValue());
}
}
});
Use the setProgress method as you are doing your processing to send the update. You could simply count how many files there are and call setProgress with (numberProcessed*100)/totalNumber. Note that the multiplication is done first to prevent rounding issues.
This will cause the PropertyChangeListeners to be notified of the changes, this will happen on the EDT so it will be safe to update your JProgressBar from it.

JProgressBar not triggering propertyChange on setProgress

I've read many different articles about JProgressBar...including the dodgy code found over at Java; here.
Most indicate you need a SwingWorker to get things happening properly, which makes perfect sense, I understand that much. I am finding that when I call setProgress(value) to update the progressbar, it's not triggering the propertyChange event most of the time. I've checked the value I'm passing to setProgess and it definitely changes every time, so I'm not sure if it's just firing the event too quickly? Please see relevant code below, any help/explanation would be greatly appreciated.
class ProgBar extends SwingWorker
{
public ProgBar()
{
addPropertyChangeListener(new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if ("progress".equals(evt.getPropertyName()))
{
int value = (Integer)evt.getNewValue();
System.out.println("propertyChange called with: " + value);
loginProg.setValue(value);
}
}
});
loginProg.setStringPainted(true);
loginProg.setValue(0);
setProgress(0);
}
#Override
public Void doInBackground() throws InterruptedException
{
...
int count = 0;
for (Folder f : folders)
{
... // process 'f'
setProgress((int)Math.min(((double)count/folders.length)*100.0, 100.0));
}
...
return null;
}
#Override
public void done()
{
System.out.println("Done called.");
setProgress(100);
loginProg.setValue(100);
}
}
JProgressBar called with this;
private void jButtonActionPerformed(java.awt.event.ActionEvent evt)
{
// Create new thread to run progess bar.
// Otherwise won't be able to update progress bar.
ProgBar pb = new ProgBar();
pb.execute();
}
}
EDIT:
Yeah, so I should have read the Javadocs better;
Because PropertyChangeListeners are notified asynchronously on the Event Dispatch Thread multiple invocations to the setProgress method might occur before any PropertyChangeListeners are invoked. For performance purposes all these invocations are coalesced into one invocation with the last invocation argument only.
For example, the following invokations:
setProgress(1);
setProgress(2);
setProgress(3);
might result in a single PropertyChangeListener notification with the value 3.
I.E. my assumption that setProgress was firing too quickly was correct. A ProgressMonitor might be a better solution.
This isn't an answer but a demonstration sscce, to show you just what I meant:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Random;
import javax.swing.*;
public class TestProgBar {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ProgBar progBar = new ProgBar();
// **** this is key and where your code may be deficient ***
JProgressBar prog = progBar.getProg();
progBar.execute();
JOptionPane.showMessageDialog(null, prog);
}
});
}
}
class ProgBar extends SwingWorker<Void, Void> {
private JProgressBar loginProg = new JProgressBar();
public ProgBar() {
addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
int value = (Integer) evt.getNewValue();
System.out.println("propertyChange called with: " + value);
loginProg.setValue(value);
}
}
});
loginProg.setStringPainted(true);
loginProg.setValue(0);
setProgress(0);
}
public JProgressBar getProg() {
return loginProg;
}
#Override
public Void doInBackground() throws InterruptedException {
int count = 0;
int max = 5;
Random random = new Random();
// simulate uploading files
while (count < 100) {
count += random.nextInt(max);
if (count > 100) {
count = 100;
}
setProgress(count);
Thread.sleep(400);
}
// for (Folder f : folders) {
// setProgress((int) Math.min(((double) count / folders.length) * 100.0,
// 100.0));
// }
return null;
}
#Override
public void done() {
System.out.println("Done called.");
setProgress(100);
loginProg.setValue(100);
}
}
Again, this code works fine, suggesting that the code you've loaded does not show the error. You need to do further work isolating the error and getting it into code so we can test it.
Yeah, so I should have read the Javadocs better;
Because PropertyChangeListeners are notified asynchronously on the Event Dispatch Thread multiple invocations to the setProgress method might occur before any PropertyChangeListeners are invoked. For performance purposes all these invocations are coalesced into one invocation with the last invocation argument only.
For example, the following invokations:
setProgress(1);
setProgress(2);
setProgress(3);
might result in a single PropertyChangeListener notification with the value 3.
I.E. my assumption that setProgress was firing too quickly was correct. A ProgressMonitor might be a better solution. I've confirmed this with the SSCCE and my program, both are simply firing setProgress too quickly and as a result, only the last value passed to setProgress is being passed through to the PropertyChange event.
If you want listeners to be called immediately, you can try the following (which worked for me):
setProgress(1);
firePropertyChange("progress", 0, 1);

Categories