使用 countDownLatch 更新两个或多个 ProgressBar

Updating two or more ProgressBar with countDownLatch

我正在学习带进度条的多线程。我在以下简单代码中毫无问题地获得了更新:

public class Controller implements Initializable {
@FXML
private Button id_boton1;
@FXML
private Button id_boton2;

@FXML
private ProgressBar id_progressbar1;
@FXML
private ProgressIndicator id_progressindicator1;

@Override
public void initialize(URL location, ResourceBundle resources) {
    id_progressbar1.setProgress(0.0);
    id_boton1.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {
            Thread hiloProgressBar= new Thread(new bg_Thread1());
            //hilo.setDaemon(true);
            hiloProgressBar.start();
        }
    });

    id_boton2.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {
            Thread hiloProgressIndicate = new Thread(new bg_Thread2());
            hiloProgressIndicate.start();
        }
    });



}

class bg_Thread1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            id_progressbar1.setProgress((float)i/99);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);

        }
    }
}

class bg_Thread2 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            id_progressindicator1.setProgress((float)i/99);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);

        }
    }
}}

没什么特别的。 2 个按钮,用于创建带有作业和进度条的线程。

我的问题是关于这个程序的CountDownLatch 的实现。我想等待两个线程完成。现在 UI 不会随着进度条的变化而刷新

public class Controller implements Initializable {
@FXML
private Button id_boton1;


@FXML
private ProgressBar id_progressbar1;
@FXML
private ProgressIndicator id_progressindicator1;

@Override
public void initialize(URL location, ResourceBundle resources) {



    CountDownLatch countDownLatch = new CountDownLatch(2);

    id_progressbar1.setProgress(0.0);
    id_boton1.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {

            System.out.println("iniciando Threads");

            Thread hiloProgressBar= new Thread(new bg_Thread1(countDownLatch));
            hiloProgressBar.start();

            Thread hiloProgressIndicate = new Thread(new bg_Thread2(countDownLatch));
            hiloProgressIndicate.start();

            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("*fin*");
        }
    });


}

class bg_Thread1 extends Thread{

    private CountDownLatch latch;

    public bg_Thread1(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            id_progressbar1.setProgress((float)i/99);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);

        }
        latch.countDown();
    }
}

class bg_Thread2 extends Thread{

    private CountDownLatch latch;

    public bg_Thread2(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        for (int i = 0; i < 40; i++) {
            id_progressindicator1.setProgress((float)i/39);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);

        }
        latch.countDown();
    }
}}

现在,一个按钮启动两个线程,当工作完成时进度条更新一次

A CountDownLatch 非常适合,例如当您想将 SQL 查询分成更小的查询,并等待所有查询返回时。然后只有在所有结果都完成后才合并您的结果。我不确定你是否真的需要它......你可以在状态为 "succeeded" 时添加一个事件处理程序。对于此示例,一旦相应线程完成,我将标签更改为 "Finished"。如果您需要等待它们全部完成才能做其他事情,那么您需要将闩锁封装在另一个线程中。 latch.await() 如果 UI 线程上的 运行 将冻结 UI。

更新: 我已经实现了 CountDownLatch,因为您需要等待两个线程完成才能执行其他操作。



import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.VBox;
import javafx.stage.*;

public class ProgressTest extends Application
{

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage)
    {

        Button getButton = new Button("Copy");

        ProgressBar progressBar = new ProgressBar(0);
        ProgressIndicator progressIndicator = new ProgressIndicator(0);
        progressBar.setProgress(0);
        progressIndicator.setProgress(0);
        Label statusLabel1 = new Label();
        Label statusLabel2 = new Label();
        VBox vBox = new VBox();
        vBox.getChildren().addAll(statusLabel1, statusLabel2, progressBar, progressIndicator, getButton);
        vBox.setAlignment(Pos.CENTER);

        getButton.setOnAction((ActionEvent e) ->
        {

            CountDownLatch countDownLatch = new CountDownLatch(2);

            // Create a Task.
            CopyTask copyTask1 = new CopyTask(30, countDownLatch);
            CopyTask copyTask2 = new CopyTask(50, countDownLatch);
            progressBar.progressProperty().unbind();
            // Bind progress property
            progressBar.progressProperty().bind(copyTask2.progressProperty());

            progressIndicator.progressProperty().unbind();
            // Bind progress property.
            progressIndicator.progressProperty().bind(copyTask1.progressProperty());

            // Unbind text property for Label.
            statusLabel1.textProperty().unbind();
            statusLabel2.textProperty().unbind();

            // Bind the text property of Label
            // with message property of Task
            statusLabel1.textProperty().bind(copyTask1.messageProperty());
            statusLabel2.textProperty().bind(copyTask2.messageProperty());

            // When completed tasks
            copyTask1.addEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, (WorkerStateEvent t) ->
            {
                statusLabel1.textProperty().unbind();
                statusLabel1.setText("Finished1");
            });
            copyTask2.addEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, (WorkerStateEvent t) ->
            {
                statusLabel2.textProperty().unbind();
                statusLabel2.setText("Finished2");

            });

            Task<Void> task = new Task<Void>()
            {
                @Override
                public Void call() throws InterruptedException
                {

                    // Start the Task.
                    new Thread(copyTask1).start();
                    new Thread(copyTask2).start();
                    countDownLatch.await();
                    return null;

                }
            };

            task.setOnSucceeded(event ->
            {
                System.out.println("This occurs after both threads complete...");

            });

            task.setOnFailed(event ->
            {
                System.out.println("FAIL...");
            });

            Thread thread = new Thread(task);
            thread.start();

        });

        final Scene scene = new Scene(vBox);

        primaryStage.setScene(scene);

        primaryStage.show();
    }

    public class CopyTask extends Task<Void>
    {

        int x;
        CountDownLatch latch;

        public CopyTask(int x, CountDownLatch latch)
        {
            super();
            this.x = x;
            this.latch = latch;

        }

        @Override
        protected Void call() throws Exception
        {
            //don't do the infitnite progress meter ...
            this.updateProgress(0, x);

            try
            {

                int len;
                for (len = 0; len <= x; len++)
                {
                    this.updateProgress(len, x);
                    Thread.sleep(100);
                    if (this.isCancelled())
                    {
                        throw new InterruptedException();
                    }
                }

                if (this.isCancelled())
                {
                    throw new InterruptedException();
                }
            } catch (InterruptedException ex)
            {
                System.out.println("Cancelled");
            }
            latch.countDown();
            return null;

        }

    }

}

当您阻塞 CountDownLatch 等待每个任务完成时,您正在阻塞 JavaFX 应用程序线程。该线程负责处理 UI 和用户事件,这意味着如果它被阻塞 none 就会发生这种情况。这就是为什么您的 UI 在等待任务完成时被冻结的原因。有关详细信息,请参阅 Concurrency in JavaFX

我链接到的页面展示了 javafx.concurrent API。它包含 类 能够以线程安全的方式与 FX 线程通信。 类 的 The Javadoc 和 API 中的接口详细解释了如何使用它们。在您的情况下,您对 Worker#progress 属性 感兴趣,您可以将 ProgressBar#progress 属性 绑定到它。然后,您将使用 Task#updateProgress 到 post 更新 FX 线程,这将导致 ProgressBar 更新。

但是,您还需要一种在每个任务完成后做某事的方法。您目前正在为每个任务使用 CountDownLatchwait。正如已经讨论过的,这是有问题的。在编写 GUI 应用程序时,您需要以更 event-driven 的方式思考。换句话说,与其等待任务完成,不如反应任务完成。跟踪有多少任务,当所有任务都完成后,执行一些操作。这是一个例子:

import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class App extends Application {

    private static final int TASK_COUNT = 6;

    private static List<Task<?>> createTasks() {
        Random random = new Random();
        return IntStream.range(0, TASK_COUNT)
                .mapToObj(i -> new MockTask(random.nextInt(4_000) + 1_000))
                .collect(Collectors.toList());
    }

    private static boolean isTerminalState(Worker.State state) {
        return state == Worker.State.SUCCEEDED
                || state == Worker.State.CANCELLED
                || state == Worker.State.FAILED;
    }

    // Assumes none of the tasks are completed yet. May wish to validate that.
    private static void onTasksComplete(Runnable action, List<? extends Task<?>> tasks) {
        // local variables must be effectively final when used in lambda expressions
        int[] count = new int[]{tasks.size()}; 
        for (Task<?> task : tasks) {
            task.stateProperty().addListener((observable, oldState, newState) -> {
                if (isTerminalState(newState) && --count[0] == 0) {
                    action.run(); // invoked on FX thread
                }
            });
        }
    }

    @Override
    public void start(Stage primaryStage) {
        List<Task<?>> tasks = createTasks();

        VBox root = new VBox(10);
        root.setPadding(new Insets(15));
        root.setAlignment(Pos.CENTER);

        for (Task<?> task : tasks) {
            ProgressBar progressBar = new ProgressBar();
            progressBar.setMaxWidth(Double.MAX_VALUE);
            // In a real application you'd probably need to unbind when the task completes
            progressBar.progressProperty().bind(task.progressProperty());
            root.getChildren().add(progressBar);
        }

        primaryStage.setScene(new Scene(root, 500, -1));
        primaryStage.setTitle("Concurrency");
        primaryStage.show();

        onTasksComplete(() -> {
            Alert alert = new Alert(AlertType.INFORMATION);
            alert.initOwner(primaryStage);
            alert.setHeaderText(null);
            alert.setContentText("All tasks have completed.");
            alert.show();
        }, tasks);

        ExecutorService executor = Executors.newFixedThreadPool(tasks.size(), r -> {
            // use daemon threads
            Thread thread = new Thread(r);
            thread.setDaemon(true);
            return thread;
        });
        tasks.forEach(executor::execute);
        executor.shutdown();
    }

    private static class MockTask extends Task<Void> {

        private final int iterations;

        MockTask(int iterations) {
            this.iterations = iterations;
        }

        @Override
        protected Void call() throws Exception {
            updateProgress(0, iterations);
            for (int i = 0; i < iterations; i++) {
                Thread.sleep(1L);
                updateProgress(i + 1, iterations);
            }
            return null;
        }

    }

}

以上是对 Worker#state 属性 变为终止状态的反应。还有其他选择。例如,您可以收听 Worker#running 属性 更改为 false 或向 Task 添加回调(例如 Task#setOnSucceeded)。