首页 > 解决方案 > 调用 completeExceptionally 时未在 CompletableFuture 上执行最后一次转换

问题描述

我有以下代码:

public final class Start {

    private static final CountDownLatch FINAL_THREAD = new CountDownLatch(1);

    private static String getValue() {
        System.out.println("Waiting...");
        try {
            Thread.sleep(Duration.ofSeconds(1).toMillis());
            return "value";
        } catch (InterruptedException e) {
            return "interrupted";
        }
    }

    private static void whenComplete(String value, Throwable ex) {
        if (ex != null) {
            System.out.println("whenComplete Ex: " + ex);
        } else {
            System.out.println("whenComplete Value: " + value);
        }
    }

    private static String handle(String value, Throwable ex) {
        if (ex != null) {
            System.out.println("handle Ex: " + ex);
        } else {
            System.out.println("handle Value: " + value);
        }
        FINAL_THREAD.countDown();
        return value;
    }

    private static String peek(String value) {
        System.out.println("peek: " + value);
        return value;
    }

    private static CompletableFuture<String> createRequest() {
        System.out.println("Create....");
        return CompletableFuture.supplyAsync(Start::getValue)
                .thenApply(Start::peek)
                .handle(Start::handle)
                .whenComplete(Start::whenComplete);
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        createRequest().completeExceptionally(new RuntimeException("TEST"));
        FINAL_THREAD.await();
    }

}

当我执行它时,我得到如下输出:

> Task :Start.main()
Create....
Waiting...
peek: value
handle Value: value

BUILD SUCCESSFUL in 10s

我不明白为什么Start::whenComplete两者都没有被Start::peek调用Start::handle。如果我用 whenComplete 切换句柄,则Start::handle不会被调用,但Start::whenComplete会。我希望在这种情况下Start::whenComplete会调用 with RuntimeExeception,而其他阶段将使用Start::getValue.

标签: javajava.util.concurrentcompletable-future

解决方案


我认为文档CompletableFuture涵盖了这一点,但让我们慢慢了解它,因为它不是那么微不足道。首先,我们需要稍微重构您的代码:

private static CompletableFuture<String> createRequest() {
    System.out.println("Create....");
    CompletableFuture<String> one = CompletableFuture.supplyAsync(Start::getValue);
    CompletableFuture<String> two = one.thenApply(Start::peek);
    CompletableFuture<String> three = two.handle(Start::handle);
    CompletableFuture<String> four = three.whenComplete(Start::whenComplete);

    return four;
}

让我们稍微改变一下你的main

public static void main(String[] args) {
    CompletableFuture<String> f = createRequest();
    boolean didI = f.completeExceptionally(new RuntimeException("TEST"));
    System.out.println("have I completed it? : " + didI);
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
}

现在让我们仔细看一下:

CompletableFuture<String> four = three.whenComplete(Start::whenComplete);

通过以下文档whenComplete

返回一个与此阶段具有相同结果或异常的新 CompletionStage,它在此阶段完成时执行给定的操作。

把它分成小块:

返回一个新的 CompletionStage ( four),其结果或异常与此阶段 ( ) 相同,在此阶段 ( ) 完成时three执行给定的操作 ( )。Start::whenCompletethree

谁应该执行Start::whenComplete?根据文档:four。它应该什么时候执行?什么时候three完成。


根据您的流程, three完成之前,您completeExceptionallyfour. 所以什么时候three完成,所以是four- 意味着它不能执行那个Start::whenCompleteaction);只是因为它已经完成了。另一种思考方式是,当您的代码到达这一行时:

CompletableFuture<String> four = three.whenComplete(Start::whenComplete);

four是一个未完成的未来。可以通过两种方式完成:

  • 无论何时three完成,从而触发Start::whenComplete

  • 外部(你做什么completeExceptionally

因为您在完成之前在 three外部完成了它,所以它不会运行该操作。


如果你将一个动作链接到你完成的未来:

four.whenComplete(Start::whenComplete);

这是您将看到所需输出的时候。


推荐阅读