使用 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
更新。
但是,您还需要一种在每个任务完成后做某事的方法。您目前正在为每个任务使用 CountDownLatch
到 wait。正如已经讨论过的,这是有问题的。在编写 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
)。
我正在学习带进度条的多线程。我在以下简单代码中毫无问题地获得了更新:
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
更新。
但是,您还需要一种在每个任务完成后做某事的方法。您目前正在为每个任务使用 CountDownLatch
到 wait。正如已经讨论过的,这是有问题的。在编写 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
)。