Issue
I' running multiple services like this: (for example to read a file in multiple threads)
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
is a class which extends the Service
class and is doing some stuff, like reading a file.
It's called from another thread which is not the JavaFX application thread.
How can I wait until all these services are done to call the System.out.println
?
reproducible example:
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;
}
};
}
}
Solution
You are starting the service from the FX Application Thread (which is where the start()
method is executed), and you must not block that thread. So one way is to create a counter for the number of services completed, and respond when it reaches zero.
Note that everything that's new here (creating and updating the servicesPending
property, and the code that's executed when the services are complete) is executed on the FX Application Thread, so this approach is appropriate if you are updating the UI when the services are complete.
@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();
}
}
On the other hand, if the work you are doing when the services are complete is not UI related, then you can create a new thread which blocks until the services are complete, and then do the work on that background thread. One way to achieve this is with a 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();
}
A similar solution is suggested in the comment by @jewelsea. If you use Task
s instead of Service
s, you can call get()
, which will block until the task completes:
@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);
}
and
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;
}
}
This solution is nice if you want to do more background work when the individual ReadService
s are all complete, and then want to do UI work after that (just use onServicesCompleted.setOnSucceeded(...)
).
Answered By - James_D
Answer Checked By - Katrina (JavaFixing Volunteer)