Chain CompletableFuture并在第一次成功时停止

时间:2018-05-24 20:34:41

标签: java java-8 completable-future

我正在使用返回CompletableFuture的API来查询设备(类似于digitalpetri modbus)。

我需要使用几个选项来调用此API来查询设备并弄清楚它是什么 - 这基本上是试验和错误,直到成功为止。这些是我无法更改的嵌入式设备协议,但您可以将该过程视为类似于以下内容:

  1. 你是苹果吗?
  2. 如果没有,那么你是菠萝吗?
  3. 如果没有,那么你是一支笔吗?
  4. ...
  5. 虽然API使用期货,但实际上,通信是串行的(通过相同的物理线路),因此它们永远不会同步执行。一旦我知道它是什么,我希望能够停止尝试并让呼叫者知道它是什么。

    我已经知道我只能通过any(见下文)得到一个期货的结果,但这可能导致应该避免的额外尝试。

    是否有一种链接期货的模式,一旦其中一个成功,你就会停止?

    类似,但浪费了非常有限的资源。

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

2 个答案:

答案 0 :(得分:1)

假设您有一个如您所描述的“伪异步”方法,即它具有异步API但需要执行某些锁定:

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

要检查此方法的List<Integer>个输入,您可以使用递归合成按顺序检查每个输入:

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

这将使用如下:

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

输出:

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

正如您所看到的,当您的API(5)保持异步时,不会调用输入findMatch()

答案 1 :(得分:0)

我认为,在您检索结果后,您可以做的最好的事情是

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

这不会影响产生结果的人,并尽力阻止其他人。既然IIUC你是从外部来源获得的,那么它并不能保证它会真正中断它们的工作。

但是,因为

  

这个类没有直接控制导致它完成的计算,取消被视为另一种形式的异常完成

(来自CompletableFuture doc),我怀疑它会做你真正想要的。