【问题标题】:Updating two or more ProgressBar with countDownLatch使用 countDownLatch 更新两个或更多 ProgressBar
【发布时间】:2019-12-28 18:39:35
【问题描述】:

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

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

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

【问题讨论】:

  • 查看Concurrency in JavaFX。你的问题是你阻塞了 JavaFX 应用程序线程,你不应该这样做。
  • 谢谢斯劳。是的,问题在于 JavaFX 应用程序线程。不幸的是,我找不到解决方案。
  • 你必须使用CountdownLatch吗?
  • 与您的问题无关:请学习 java 命名约定并遵守它们。你快到了,现在用驼峰式替换下划线,它是完美的:)
  • 我想使用 CountdownLach 或其他等价物。

标签: java multithreading user-interface javafx countdownlatch


【解决方案1】:

CountDownLatch 非常适合,例如,当您想将 SQL 查询分成更小的查询并等待所有查询返回时。然后仅在所有结果都完成后合并您的结果。我不确定你是否真的需要它......你可以在状态“成功”时添加一个事件处理程序。对于此示例,我在相应线程完成后将标签更改为“已完成”。如果您需要等待所有这些都完成以执行其他操作,那么您需要将闩锁封装在另一个线程中。 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 与 UI 线程不兼容,这是真的吗?非常感谢。我会研究你的例子。因为我的声誉很低,所以我不能投票给你的答案。
  • @agriarte 正确,CountDownLatch 等对象与在 FX 线程上使用不兼容(尽管后台线程可以等待 FX 线程调用 countDown(),即与您的操作相反) '正在尝试做)。我添加了一个答案,展示了如何在不使用 CountDownLatch 的情况下等待多个任务。
【解决方案2】:

当您阻塞CountDownLatch 等待每个任务完成时,您将阻塞JavaFX 应用程序线程。该线程负责处理 UI 和用户事件,这意味着如果它被阻止,这一切都不会发生。这就是您的 UI 在等待任务完成时冻结的原因。如需更多信息,请参阅Concurrency in JavaFX

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

但是,您还需要一种在每项任务完成后做某事的方法。您目前正在为每个任务使用CountDownLatch等待。正如已经讨论过的,这是有问题的。在编写 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)。

【讨论】:

    猜你喜欢
    • 2012-09-08
    • 1970-01-01
    • 2018-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-29
    • 2020-09-20
    • 2014-06-02
    相关资源
    最近更新 更多