简体   繁体   中英

JavaFX : Running parallel threads and update overall progress to a ProgressBar

I'm in need of splitting some workload to threads and start them parallel as they are independent. I also want to display overall progress in a ProgressBar with JavaFx. It means progressbar shows the total work done by each thread so far .

For sake of simplicity we can take this Counter class as an example

public class Counter implements Runnable {
    private int from, to;

    public Counter(int from, int to) {
        this.from = from;
        this.to = to;
    }

    @Override
    public void run() {
        for (int i = from; i < to ; i++) {
            // Do some heavy operation
            // report about progress to the parent
        }

        // exit thread with status of success or failure
    }
}

This class takes from, to as boundary conditions.

To not block the UI I use a simple Task class like this

public class MyTask extends Task<Integer> {
    int iter;

    public MyTask(int iter) {
        this.iter = iter;
    }

    @Override
    protected Integer call() throws Exception {
        // Simply divide work load to each thread
        int workers = 8;
        int limit = iter/workers;
        int rem = iter % workers;

        // Creates a executor
        ExecutorService executorService = Executors.newFixedThreadPool(workers);
        for (int i = 0; i < workers; i++) {
            int start = limit * i;
            int end = limit * (i + 1);
            if (i == workers - 1) end += rem;

            Counter counter = new Counter(start, end);
            executorService.submit(counter); // Submit work to be done
        }

        executorService.shutdown(); // start the execution

        // Get their progress, update overall progress to UI
        // Stop after all threads finished
    }
}

In the MyTask I want to update the UI as stated in the comments with the overall completion. (ie Total count done by each thread).

Is there any method to do this? aggregate the progress of parallel tasks and update overall progress in UI(im not counting on the number of finished threads, it's the each thread's current progress I want to report to the MyTask).

Depending on the number of tasks running in parallel you may simply use Task s for the Counter logic and update the overall progress from listeners to the progress properties of those tasks.

If too many of those tasks are running in parallel though, this could slow down the JavaFX application thread, since too many Runnable s could be waiting to be executed at once.

You could implement updates based on the progress difference yourself though by using a synchronized statement. The following code starts the updates from the javafx application thread for simplicity, but this logic could be moved to a different thread without causing any issues:

@Override
public void start(Stage primaryStage) {
    ProgressBar progressBar = new ProgressBar();

    int workers = 8;

    ExecutorService executorService = Executors.newFixedThreadPool(workers);

    final int taskCount = 12;
    final int elementsPerTask = 50;
    final int elementCount = elementsPerTask * taskCount;

    ProgressReceiver progressReceiver = new ProgressReceiver() {

        private boolean updating = false;
        private int progress = 0;

        @Override
        public void acceptProgress(int oldValue, int newValue) {
            synchronized(this) {
                progress += newValue - oldValue;
                if (!updating) {
                    updating = true;
                    Platform.runLater(() -> {
                        synchronized (this) {
                            updating = false;
                            progressBar.setProgress(((double) progress) / elementCount);
                        }
                    });
                }
            }
        }

    };
    for (int i = 0; i < taskCount; i++) {
        int start = elementsPerTask * i;
        int end = elementsPerTask * (i + 1);

        Counter counter = new Counter(start, end, progressReceiver);
        executorService.submit(counter);
    }

    executorService.shutdown();

    StackPane root = new StackPane(progressBar);

    Scene scene = new Scene(root, 300, 300);

    primaryStage.setScene(scene);
    primaryStage.show();
}
public interface ProgressReceiver {
    void acceptProgress(int oldValue, int newValue);
}
public class Counter implements Runnable {

    private final ProgressReceiver progressReceiver;

    private final int from, to;

    public Counter(int from, int to, ProgressReceiver progressReceiver) {
        this.from = from;
        this.to = to;
        this.progressReceiver = progressReceiver;
    }

    @Override
    public void run() {
        for (int i = from; i < to; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
            int oldProgress = i - from;
            progressReceiver.acceptProgress(oldProgress, oldProgress + 1);
        }

        // exit thread with status of success or failure
    }
}

I resolved the issue using PropertyChangeSupport class. This provides thread safe properties, also it provide property listeners. More from here .

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public final class ProgressReporter {
    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
    private int progress = 0;

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        propertyChangeSupport.removePropertyChangeListener(listener);
    }

    public void accumulateProgress(int progress){
        this.propertyChangeSupport.firePropertyChange("progress", this.progress, this.progress + progress);
        this.progress += progress;
    }

    public int getProgress() {
        return progress;
    }
}

Now by listening to the ProgressReporter we can get the progress when new data arrives. Note that firePropertyChange only fires when old and new values are different , otherwise it will not fire an update to listeners.

Now we create the Counter class to use this ProgressReporter

public class Counter implements Runnable {
    private int id, from, to, sleep;
    private ProgressReporter reporter;

    public Counter(int id, int from, int to, int sleep, ProgressReporter reporter) {
        this.from = from;
        this.to = to;
        this.sleep = sleep;
        this.id = id;
        this.reporter = reporter;

        System.out.println("Thread #" + id + " started delay=" + sleep);
    }

    @Override
    public void run() {
        for (int i = from; i < to ; i++) {
            try {
                Thread.sleep(sleep);
                reporter.accumulateProgress(1); // this will fire an update to listeners
            } catch (InterruptedException e){

            }
        }

        System.out.println("Thread #" + id + " is completed");
    }
}

Now in the Task which initiated by the JavaFX thread implemented like this.

public class MyTask extends Task<Integer> {
    int iterations;
    Random random = new Random();
    ProgressReporter reporter = new ProgressReporter();

    public MyTask(int iterations) {
        this.iterations = iterations;
    }

    @Override
    protected Integer call() throws Exception {
        // Simply divide work load to each thread
        int workers = 8;
        int limit = iterations /workers;
        int rem = iterations % workers;

        // add a property listener for progress update
        reporter.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                updateProgress((int) evt.getNewValue(), iterations);
            }
        });

        // Creates a executor
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < workers; i++) {
            int start = limit * i;
            int end = limit * (i + 1);
            if (i == workers - 1) end += rem;

            Counter counter = new Counter(i ,start, end, random.nextInt(1000), reporter);
            executorService.submit(counter); // Submit work to be done
        }

        executorService.shutdown(); // shutdown executor not to accept any more threads
        while (!executorService.isTerminated()){
            if (isCancelled()){
                executorService.shutdownNow(); // stop all the processes immediately
            }
        }

        return reporter.getProgress();
    }
}

Now the usual JavaFX bindings like

progressBar.progressProperty().bind(task.progressProperty())

Full source code can be found in here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM