Enrico
Enrico

Reputation: 445

How to wait for multiple services to complete?

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

Upvotes: 0

Views: 722

Answers (1)

James_D
James_D

Reputation: 209653

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 Tasks instead of Services, 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 ReadServices are all complete, and then want to do UI work after that (just use onServicesCompleted.setOnSucceeded(...)).

Upvotes: 1

Related Questions