Long running and blocking javafx task progress bar

2019-07-20 09:16发布

I have a task that can be long running, and it's blocking. I must wait for this task to complete to display results on the GUI.
I know how long this task will take, so I want to notify user with a progress bar, how much time is left.
All the examples on the internet are like this:

Task<Integer> task = new Task<Integer>() {
    @Override
    protected Integer call() throws Exception {
         int iterations;
         for (iterations = 0; iterations < 1000; iterations++) {
             updateProgress(iterations, 1000);

             // Now block the thread for a short time, but be sure
             // to check the interrupted exception for cancellation!
             try {
                 Thread.sleep(100);
             } catch (InterruptedException interrupted) {
                 if (isCancelled()) {
                     updateMessage("Cancelled");
                     break;
                 }
             }
         }
         return iterations;
     }
 };


This code blocks in a loop, and update it's progress with updateProgress method.
My case would look like:

Task<Integer> task = new Task<Integer>() {
    @Override
    protected Integer call() throws Exception {
             try {
                 //This is my long running task.
                 Thread.sleep(10000);
             } catch (InterruptedException interrupted) {
                 if (isCancelled()) {
                     updateMessage("Cancelled");
                     return null;
                 }
             }
         }
         return iterations;
     }
 };


This doesn't work in a loop, so I can't update progress with updateProgress.
One solution to this would be to create another task that would run my blocking task, and would count progress, but this seems inelegant.
Is there a better way to do this?

2条回答
一夜七次
2楼-- · 2019-07-20 09:55

For these kind of requirements, I would advice you to use Infinite ProgressIndicator.

enter image description here

It helps to show user that some background process is taking place and when finished the ProgressIndicator will be removed.

查看更多
疯言疯语
3楼-- · 2019-07-20 10:13

I find it hard to see a use case in which the task blocks but you somehow know how long it would take (at least one that wouldn't be easier implemented with the animation API). But assuming you have one, I would approach it like this:

Your task is blocking, so updates to the progress must be performed elsewhere. Basically, you would want a background thread that periodically updates the progress. The optimal time to do this would be once every time a frame is rendered to the screen: the AnimationTimer class is designed exactly for this. So, you want the task to start an animation timer when it starts running, update the progress in the handle(...) method, and make sure it stops when the task stops running. This looks like:

public abstract class FixedDurationTask<V> extends Task<V> {

    private final Duration anticipatedRuntime ;
    private AnimationTimer timer ;

    public FixedDurationTask(Duration anticipatedRuntime) {
        this.anticipatedRuntime = anticipatedRuntime ;
    }

    public Duration getAnticipatedRuntime() {
        return anticipatedRuntime ;
    }

    @Override
    protected void running() {
        timer = new AnimationTimer() {
            long startTime = System.nanoTime() ;
            long endTime = (long) (startTime + anticipatedRuntime.toMillis() * 1_000_000) ;
            long duration = endTime - startTime ;

            @Override
            public void handle(long now) {
                if (isRunning()) {
                    updateProgress(now - startTime, duration);
                } else {
                    stop();
                    if (getState() == State.SUCCEEDED) {
                        updateProgress(duration, duration);
                    }
                }
            }
        };
        timer.start();
    }
}

Here is a minimal test (with the above class included for convenience). Type a time in seconds in the text field and press Enter.

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class FixedDurationTaskTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        TextField durationField = new TextField();
        durationField.setPromptText("Enter time in seconds");
        Service<Void> service = new Service<Void>() {

            @Override
            protected Task<Void> createTask() {
                double duration = Double.parseDouble(durationField.getText());
                return new FixedDurationTask<Void>(Duration.seconds(duration)) {
                    @Override
                    public Void call() throws Exception {
                        Thread.sleep((long) getAnticipatedRuntime().toMillis());
                        return null ;
                    }
                };
            }

        };

        ProgressBar progress = new ProgressBar();
        progress.progressProperty().bind(service.progressProperty());

        durationField.disableProperty().bind(service.runningProperty());

        durationField.setOnAction(e -> service.restart());

        VBox root = new VBox(10, durationField, progress);
        root.setPadding(new Insets(20));
        root.setMinHeight(60);
        root.setAlignment(Pos.CENTER);

        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    public static abstract class FixedDurationTask<V> extends Task<V> {

        private final Duration anticipatedRuntime ;
        private AnimationTimer timer ;

        public FixedDurationTask(Duration anticipatedRuntime) {
            this.anticipatedRuntime = anticipatedRuntime ;
        }

        public Duration getAnticipatedRuntime() {
            return anticipatedRuntime ;
        }

        @Override
        protected void running() {
            timer = new AnimationTimer() {
                long startTime = System.nanoTime() ;
                long endTime = (long) (startTime + anticipatedRuntime.toMillis() * 1_000_000) ;
                long duration = endTime - startTime ;

                @Override
                public void handle(long now) {
                    if (isRunning()) {
                        updateProgress(now - startTime, duration);
                    } else {
                        stop();
                        if (getState() == State.SUCCEEDED) {
                            updateProgress(duration, duration);
                        }
                    }
                }
            };
            timer.start();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
查看更多
登录 后发表回答