Выполнение и ожидание нескольких параллельных и последовательных задач с использованием Arraylist of Tasks в JavaFX - PullRequest
1 голос
/ 06 апреля 2020

Я ищу подходящий способ для отображения времени обработки параллельных запущенных задач на отдельной стадии.

Я хочу выполнить различные задачи, объединенные в ArrayList - одну за другой. Для этого случая я использую ThreadPool. После каждого выполненного списка я хочу дождаться завершения всех задач. Только когда задачи достигли статуса «успешно», я хочу что-то сделать в MainThread. После этого я хочу выполнить еще один список задач и визуализировать их также на отдельной стадии. На следующем рисунке показана желаемая последовательность обработки (в зависимости от исходного кода, указанного ниже): введите здесь описание изображения

Для этого я написал классы MyLoader. MyLoader-класс содержит отдельную задачу и связывает свойства прогресса с меткой и индикатором выполнения в конструкторе:

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());
    } 
}

В MainClass я объединяю несколько экземпляров MyLoader в ArrayList и запускаю их с помощью ExecutorService. Для создания нового этапа я использую stati c метод progressStage (List). Каждый этап отображается до того, как ExecutorService выполнит соответствующие задачи. Вот код MainClass:

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; 
    } 
}

Программа пока выполняется, но последовательность вычислений выглядит полностью параллельной.

То, что я попробовал:

1) До сих пор мне удалось заставить процесс считываться и останавливаться с помощью метода get () , Но тогда сцена отображается только тогда, когда потоки в фоновом режиме закончили свою работу.

//1) TODO: „doSomeThing1()“
List<Integer> integers = new ArrayList<Integer>(); 

for(MyLoader ml : firstLoaders)
    integers.add(ml.task.get());

System.out.println(integers.toString());

2) Также с помощью метода Task.setOnSucceded () я пока не смог получить никаких полезных результатов. Главным образом потому, что этап показан только после вычислений. Проблема в том, что я не могу запросить состояние всех задач в определенное время.

3) Применение CountDownLatch также достигло сопоставимого результата.

4) Кроме того , метод shutdown () в ExecutorService вызывает завершение. Поэтому это решение также не подходит.

//1) TODO: „doSomeThing1()“
es.shutdown();
try {
    es.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); 

    //SecondLoaders
    //...

}catch (InterruptedException e) {
    e.printStackTrace();
}

Есть ли подходящий подход для таких намерений? До сих пор я не пришел ни к какому полезному результату.

1 Ответ

3 голосов
/ 06 апреля 2020

Когда задача выполнена, просто обновите счетчик и проверьте, была ли последняя выполненная задача в текущем наборе.

Следующий код демонстрирует это. (Конечно, в коде есть вещи, которые можно улучшить, но концепция должна стать понятной.)

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;
    }

}

Приведенный выше код не использует возможность отмены задач / задач, завершающихся с исключением.

...