如何等待多个服务完成?
How to wait for multiple services to complete?
我' 运行 多个这样的服务:
(例如在多线程中读取一个文件)
for (int i = 0; i < 3; i++) {
ReadService readService = new ReadService();
readService.start();
}
//wait until all services have been completed
System.out.println("All services done!");
ReadService
是一个 class,它扩展了 Service
class 并且正在做一些事情,比如读取文件。
它是从另一个不是 JavaFX 应用程序线程的线程调用的。
我如何才能等到所有这些服务完成后才能调用 System.out.println
?
可重现示例:
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.stage.Stage;
public class HelloApplication extends Application {
@Override
public void start(Stage stage) {
for (int i = 0; i < 3; i++) {
ReadService readService = new ReadService();
readService.start();
}
// wait until all services have been completed
System.out.println("Services done");
// continue with some other code, for example check the read file on anything
}
public static void main(String[] args) {
launch();
}
}
class ReadService extends Service<Boolean> {
@Override
protected Task<Boolean> createTask() {
return new Task<>() {
@Override
protected synchronized Boolean call() throws Exception {
// do something, for example read a file
System.out.println("wait...");
wait(5000);
System.out.println("waiting done");
return null;
}
};
}
}
您正在从 FX 应用程序线程(执行 start()
方法的地方)启动服务,并且您不得阻塞该线程。所以一种方法是为已完成的服务数量创建一个计数器,并在它达到零时响应。
请注意,这里的所有新内容(创建和更新 servicesPending
属性,以及服务完成时执行的代码)都在 FX 应用程序线程上执行,因此这种方法是如果您在服务完成后更新 UI,则适用。
@Override
public void start(Stage stage) {
int numServices = 3 ;
IntegerProperty servicesPending = new SimpleIntegerProperty(numServices);
servicesPending.addListener((obs, oldValue, newValue) -> {
if (newValue == 0) {
// code to execute when all services are complete
System.out.println("Services done");
}
});
for (int i = 0; i < numServices; i++) {
ReadService readService = new ReadService();
readService.setOnSucceeded(e -> servicesPending.set(servicesPending.get() - 1));
readService.start();
}
}
另一方面,如果您在服务完成时所做的工作与 UI 无关,那么您可以创建一个新线程,该线程会阻塞直到服务完成,然后再进行工作在那个后台线程上。实现此目的的一种方法是使用 CountDownLatch
:
@Override
public void start(Stage stage) {
int numServices = 3 ;
CountDownLatch latch = new CountDownLatch(numServices);
for (int i = 0; i < numServices; i++) {
ReadService readService = new ReadService();
readService.setOnSucceeded(e -> latch.countDown());
readService.start();
}
Thread onServicesCompleted = new Thread(() -> {
try {
latch.await();
} catch(InterruptedException exc) {
Thread.currentThread().interrupt();
}
System.out.println("Services done");
// other work to do when services are complete...
});
onServicesCompleted.start();
}
@jewelsea 的评论中提出了类似的解决方案。如果您使用 Task
s 而不是 Service
s,您可以调用 get()
,它将阻塞直到任务完成:
@Override
public void start(Stage stage) {
int numServices = 3 ;
List<Task<Boolean>> tasks = new ArrayList<>();
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < numServices; i++) {
ReadService readService = new ReadService();
tasks.add(readService);
}
exec.invokeAll(tasks);
Task<Void> onServicesCompleted = new Task<>() {
@Override
protected Void call() throws Exception {
for (Task<Boolean> task : tasks) {
task.get();
}
System.out.println("Services Done");
// other work to be done...
}
};
exec.execute(onServicesCompleted);
}
和
class ReadService extends Task<Boolean> {
@Override
protected synchronized Boolean call() throws Exception {
// do something, for example read a file
System.out.println("wait...");
wait(5000);
System.out.println("waiting done");
return null;
}
}
如果您想在单个 ReadService
都完成后做更多的后台工作,然后想在之后做 UI 工作(只需使用 onServicesCompleted.setOnSucceeded(...)
).
我' 运行 多个这样的服务: (例如在多线程中读取一个文件)
for (int i = 0; i < 3; i++) {
ReadService readService = new ReadService();
readService.start();
}
//wait until all services have been completed
System.out.println("All services done!");
ReadService
是一个 class,它扩展了 Service
class 并且正在做一些事情,比如读取文件。
它是从另一个不是 JavaFX 应用程序线程的线程调用的。
我如何才能等到所有这些服务完成后才能调用 System.out.println
?
可重现示例:
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.stage.Stage;
public class HelloApplication extends Application {
@Override
public void start(Stage stage) {
for (int i = 0; i < 3; i++) {
ReadService readService = new ReadService();
readService.start();
}
// wait until all services have been completed
System.out.println("Services done");
// continue with some other code, for example check the read file on anything
}
public static void main(String[] args) {
launch();
}
}
class ReadService extends Service<Boolean> {
@Override
protected Task<Boolean> createTask() {
return new Task<>() {
@Override
protected synchronized Boolean call() throws Exception {
// do something, for example read a file
System.out.println("wait...");
wait(5000);
System.out.println("waiting done");
return null;
}
};
}
}
您正在从 FX 应用程序线程(执行 start()
方法的地方)启动服务,并且您不得阻塞该线程。所以一种方法是为已完成的服务数量创建一个计数器,并在它达到零时响应。
请注意,这里的所有新内容(创建和更新 servicesPending
属性,以及服务完成时执行的代码)都在 FX 应用程序线程上执行,因此这种方法是如果您在服务完成后更新 UI,则适用。
@Override
public void start(Stage stage) {
int numServices = 3 ;
IntegerProperty servicesPending = new SimpleIntegerProperty(numServices);
servicesPending.addListener((obs, oldValue, newValue) -> {
if (newValue == 0) {
// code to execute when all services are complete
System.out.println("Services done");
}
});
for (int i = 0; i < numServices; i++) {
ReadService readService = new ReadService();
readService.setOnSucceeded(e -> servicesPending.set(servicesPending.get() - 1));
readService.start();
}
}
另一方面,如果您在服务完成时所做的工作与 UI 无关,那么您可以创建一个新线程,该线程会阻塞直到服务完成,然后再进行工作在那个后台线程上。实现此目的的一种方法是使用 CountDownLatch
:
@Override
public void start(Stage stage) {
int numServices = 3 ;
CountDownLatch latch = new CountDownLatch(numServices);
for (int i = 0; i < numServices; i++) {
ReadService readService = new ReadService();
readService.setOnSucceeded(e -> latch.countDown());
readService.start();
}
Thread onServicesCompleted = new Thread(() -> {
try {
latch.await();
} catch(InterruptedException exc) {
Thread.currentThread().interrupt();
}
System.out.println("Services done");
// other work to do when services are complete...
});
onServicesCompleted.start();
}
@jewelsea 的评论中提出了类似的解决方案。如果您使用 Task
s 而不是 Service
s,您可以调用 get()
,它将阻塞直到任务完成:
@Override
public void start(Stage stage) {
int numServices = 3 ;
List<Task<Boolean>> tasks = new ArrayList<>();
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < numServices; i++) {
ReadService readService = new ReadService();
tasks.add(readService);
}
exec.invokeAll(tasks);
Task<Void> onServicesCompleted = new Task<>() {
@Override
protected Void call() throws Exception {
for (Task<Boolean> task : tasks) {
task.get();
}
System.out.println("Services Done");
// other work to be done...
}
};
exec.execute(onServicesCompleted);
}
和
class ReadService extends Task<Boolean> {
@Override
protected synchronized Boolean call() throws Exception {
// do something, for example read a file
System.out.println("wait...");
wait(5000);
System.out.println("waiting done");
return null;
}
}
如果您想在单个 ReadService
都完成后做更多的后台工作,然后想在之后做 UI 工作(只需使用 onServicesCompleted.setOnSucceeded(...)
).