Execute and wait for multiple parallel and sequential Tasks by using a Arraylist of Tasks in JavaFX

我怕爱的太早我们不能终老 提交于 2021-01-28 09:36:30

问题


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.


回答1:


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.



来源:https://stackoverflow.com/questions/61064402/execute-and-wait-for-multiple-parallel-and-sequential-tasks-by-using-a-arraylist

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!