调用 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::whenCompleteStart::peek 都被调用时不调用 Start::handle。如果我使用 whenComplete 切换句柄,则不会调用 Start::handle,但会调用 Start::whenComplete。我希望在这种情况下 Start::whenComplete 将使用 RuntimeExeception 调用,而其他阶段将使用 Start::getValue 提供的值执行。

解决方法

我认为 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,在此阶段完成时执行给定的操作。

把它分成小块:

返回与此阶段 (four) 具有相同结果或异常的新 CompletionStage (three),当此阶段 (Start::whenComplete) 执行给定操作 (three) }}) 完成。

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


根据您的流程, three 完成之前,您completeExceptionally 您的four。因此,当 three 完成时,four 也完成 - 意味着它无法执行 Start::whenComplete (action);只是因为它已经完成了。另一种思考方式是,当您的代码到达这一行时:

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

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

  • 要么在three完成时触发Start::whenComplete

  • 外部(你用 completeExceptionally 做什么)

因为您在外部 three完成之前完成它,所以它不会运行那个动作。


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

four.whenComplete(Start::whenComplete);

此时您将看到所需的输出。