简体   繁体   中英

Chain CompletableFuture and stop on first success

I'm consuming an API that returns CompletableFuture s for querying devices (similar to digitalpetri modbus ).

I need to call this API with a couple of options to query a device and figure out what it is - this is basically trial and error until it succeeds. These are embedded device protocols that I cannot change, but you can think of the process as working similar to the following:

  1. Are you an apple?
  2. If not, then are you a pineapple?
  3. If not, then are you a pen?
  4. ...

While the API uses futures, in reality, the communications are serial (going over the same physical piece of wire), so they will never be executed synchronously. Once I know what it is, I want to be able to stop trying and let the caller know what it is.

I already know that I can get the result of only one of the futures with any (see below), but that may result in additional attempts that should be avoided.

Is there a pattern for chaining futures where you stop once one of them succeeds?

Similar, but is wasteful of very limited resources.

List<CompletableFuture<String>> futures = Arrays.asList(
    CompletableFuture.supplyAsync(() -> "attempt 1"),
    CompletableFuture.supplyAsync(() -> "attempt 2"),
    CompletableFuture.supplyAsync(() -> "attempt 3"));

CompletableFuture<String>[] futuresArray = (CompletableFuture<String>[]) futures.toArray();
CompletableFuture<Object> c = CompletableFuture.anyOf(futuresArray);

I think the best you can do is, after your retrieval of the result,

futures.forEach(f -> f.cancel(true));

This will not affect the one having produced the result, and tries its best to stop the others. Since IIUC you get them from an outside source, there's no guarantee it will actually interrupt their work.

However, since

this class has no direct control over the computation that causes it to be completed, cancellation is treated as just another form of exceptional completion

(from CompletableFuture doc), I doubt it will do what you actually want.

Suppose that you have a method that is "pseudo-asynchronous" as you describe, ie it has an asynchronous API but requires some locking to perform:

private final static Object lock = new Object();

private static CompletableFuture<Boolean> pseudoAsyncCall(int input) {
    return CompletableFuture.supplyAsync(() -> {
                synchronized (lock) {
                    System.out.println("Executing for " + input);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return input > 3;
                }
            });
}

And a List<Integer> of inputs that you want to check against this method, you can check each of them in sequence with recursive composition:

public static CompletableFuture<Integer> findMatch(List<Integer> inputs) {
    return findMatch(inputs, 0);
}

private static CompletableFuture<Integer> findMatch(List<Integer> inputs, int startIndex) {
    if (startIndex >= inputs.size()) {
        // no match found -- an exception could be thrown here if preferred
        return CompletableFuture.completedFuture(null);
    }
    return pseudoAsyncCall(inputs.get(startIndex))
            .thenCompose(result -> {
                if (result) {
                    return CompletableFuture.completedFuture(inputs.get(startIndex));
                } else {
                    return findMatch(inputs, startIndex + 1);
                }
            });
}

This would be used like this:

public static void main(String[] args) {
    List<Integer> inputs = Arrays.asList(0, 1, 2, 3, 4, 5);
    CompletableFuture<Integer> matching = findMatch(inputs);

    System.out.println("Found match: " + matching.join());
}

Output:

Executing for 0
Executing for 1
Executing for 2
Executing for 3
Executing for 4
Found match: 4

As you can see, it is not called for input 5 , while your API ( findMatch() ) remains asynchronous.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM