Java 8 option for each progress bar

For performance reasons, I would like to use a forEach loop of a parallel Lambda thread to process an instance Collection

in Java. Since this is done in the background Service

, I would like to use a method updateProgress(double,double)

to inform the user of the current progress.

To indicate the current progress, I need a specific progress indicator in the form of a counter Integer

. However, this is not possible as I can only access the variables final

in the Lambda expression.

See code example below Collection

- this is just a place for any possible instance Collection

:

int progress = 0;
Collection.parallelStream().forEach(signer -> {
   progress++;
   updateProgress(progress, Collection.size());     
});

      

I know that I can solve this problem using a simple for-loop. However, for performance reasons, it would be nice to solve it this way.

Does anyone know of a more or less neat solution?

+3


source to share


2 answers


As suggested with labels, using AtomicInteger is a good solution:

AtomicInteger progress = new AtomicInteger();
Collection.parallelStream().forEach(signer -> {
    progress.incrementAndGet();
    // do some other useful work
});

      

I would not use the runLater () option as your goal is high performance and if many concurrent threads generate JavaLX "runLater" tasks you will bottleneck again ...



For the same reason, I won't call the update for the ProgressBar every time, but use the JavaFX timeline for the seaparte to update the progress bar regularly regardless of the processing threads.

Here is the complete code comparing sequential versus parallel processing with ProgressBar. If you remove hibernation (1) and set the item count to 10 million, it works simultaneously and efficiently ...

public class ParallelProgress extends Application {
static class ParallelProgressBar extends ProgressBar {
    AtomicInteger myDoneCount = new AtomicInteger();
    int           myTotalCount;
    Timeline      myWhatcher = new Timeline(new KeyFrame(Duration.millis(10), e -> update()));

    public void update() {
        setProgress(1.0*myDoneCount.get()/myTotalCount);
        if (myDoneCount.get() >= myTotalCount) {
            myWhatcher.stop();
            myTotalCount = 0;
        }
    }

    public boolean isRunning() { return myTotalCount > 0; }

    public void start(int totalCount) {
        myDoneCount.set(0);
        myTotalCount = totalCount;
        setProgress(0.0);
        myWhatcher.setCycleCount(Timeline.INDEFINITE);
        myWhatcher.play();
    }

    public void add(int n) {
        myDoneCount.addAndGet(n);
    }
}

HBox testParallel(HBox box) {
    ArrayList<String> myTexts = new ArrayList<String>();

    for (int i = 1; i < 10000; i++) {
        myTexts.add("At "+System.nanoTime()+" ns");
    }

    Button runp = new Button("parallel");
    Button runs = new Button("sequential");
    ParallelProgressBar progress = new ParallelProgressBar();

    Label result = new Label("-");

    runp.setOnAction(e -> {
        if (progress.isRunning()) return;
        result.setText("...");
        progress.start(myTexts.size());

        new Thread() {
            public void run() {
                long ms = System.currentTimeMillis();
                myTexts.parallelStream().forEach(text -> {
                    progress.add(1);
                    try { Thread.sleep(1);} catch (Exception e1) { }
                });
                Platform.runLater(() -> result.setText(""+(System.currentTimeMillis()-ms)+" ms"));
            }
        }.start();
    });

    runs.setOnAction(e -> {
        if (progress.isRunning()) return;
        result.setText("...");
        progress.start(myTexts.size());
        new Thread() {
            public void run() {
                final long ms = System.currentTimeMillis();
                myTexts.forEach(text -> {
                    progress.add(1);
                    try { Thread.sleep(1);} catch (Exception e1) { }
                });
                Platform.runLater(() -> result.setText(""+(System.currentTimeMillis()-ms)+" ms"));
            }
        }.start();
    });

    box.getChildren().addAll(runp, runs, progress, result);
    return box;
}


@Override
public void start(Stage primaryStage) throws Exception {        
    primaryStage.setTitle("ProgressBar's");

    HBox box = new HBox();
    Scene scene = new Scene(box,400,80,Color.WHITE);
    primaryStage.setScene(scene);

    testParallel(box);

    primaryStage.show();   
}

public static void main(String[] args) { launch(args); }
}

      

+6


source


The naive solution would be to have progress as the field of some surrounding object; then referring to progress

from the lambda closure actually means this.progress

where this

equals final

, so the compiler won't complain. However, the resulting code will access the field progress

from multiple threads at the same time, which can lead to race conditions. I suggest restricting access to the field progress

to the JavaFX Application Stream using Platform.runLater

. Then the whole solution will look like this:



// accessed only on JavaFX application thread
private int progress = 0;

// invoked only on the JavaFX application thread
private void increaseProgress() {
    progress++;
    updateProgress(progress, collection.size());
}

private void processCollection() {
    collection.parallelStream().forEach(signer -> {
        // do the work (on any thread)
        // ...

        // when done, update progress on the JavaFX thread
        Platfrom.runLater(this::increaseProgress);
    });
}

      

+1


source







All Articles