Wait for every Future in a List<Future> to be done

17,436

Solution 1

Your code can be simplified a lot. An equivalent version could be written as follows, unless you have requirements that you didn't specify in the question.

List<Principal> users = // fill users
List<Future<UserRecord>> futures = getAllTheFutures(users);
List<UserRecord> results = new ArrayList<>();

for (int i = 0; i < futures.size(); i++) {
        try {
            results.add(futures.get(i).get());
        } catch (InterruptedException | ExecutionException e) {
            // Handle appropriately, results.add(null) or just leave it out
        }
    }
}

Solution 2

You could simply do a reductive list; removing successful responses from your list and iterating until empty.

List<Principal> users = // fill users
List<Future<UserRecord>> futures = getAllTheFutures(users);
List<UserRecord> results = new ArrayList<>();

for (int i = 0; i < futures.size(); i++) {
        try {
            results.add(futures.get(i).get(<how long you want before your application throws exception>));

        }
        catch (InterruptedException | ExecutionException e) {
            // Handle appropriately, results.add(null) or just leave it out
        }
        catch (TimeoutException timeoutEx) {
            // If the Future retrieval timed out you can handle here
        }

    }
}

Since your intent is to collect a "set" of Jobs before progressing, waiting until you get a return on thread index X in this case will give a time cost of (roughly) the last returned thread.

Or, if you plan to abort all threads in the set if any fail, you can use Java 8 CompletableFuture

CompletableFuture[] cfs = futures.toArray(new CompletableFuture[futures.size()]);

    return CompletableFuture.allOf(cfs)
            .thenApply(() -> futures.stream()
                                    .map(CompletableFuture::join)
                                    .collect(Collectors.toList())
            );
  • credit to Kayaman for simplified code base.
Share:
17,436
markvgti
Author by

markvgti

Updated on August 04, 2022

Comments

  • markvgti
    markvgti over 1 year

    I call a method that returns a Future, once for each element in a List<Principal>, so I end up with a List<Future<UserRecord>>.

    The method returning Future is library code and I have no control over how that code gets run, all I have is the Future.

    I want to wait for all the Futures to finish (success or failure) before proceeding further.

    Is there a better way to do so than this:

    List<Principal> users = new ArrayList<>();
    // Fill users
    List<Future<UserRecord>> futures = getAllTheFutures(users);
    List<UserRecord> results = new ArrayList<>(futures.size());
    boolean[] taskCompleted = new boolean[futures.size()];
    for (int j = 0; j < taskCompleted.length; j++) {
        taskCompleted[j] = false;
    }
    do {
        for (int i = 0; i < futures.size(); i++) {
            if (!taskCompleted[i]) {
                try {
                    results.add(i, futures.get(i).get(20, TimeUnit.MILLISECONDS));
                    taskCompleted[i] = true;
                } catch (TimeoutException e) {
                    // Do nothing
                } catch (InterruptedException | ExecutionException e) {
                    // Handle appropriately, then...
                    taskCompleted[i] = true;
                }
            }
        }
    } while (allNotCompleted(taskCompleted));
    

    For the curious:

    private boolean allNotCompleted(boolean[] completed) {
        for (boolean b : completed) {
            if (!b)
                return true;
        }
        return false;
    }
    

    Unlike in this answer to Waiting on a list of Future I don't have control over the code that creates the Futures.