java - java.util.concurrent.CompletableFuture 中的异常传播

标签 java java.util.concurrent completable-future forkjoinpool

有两段代码。

在第一个中,我们从总是抛出一些异常的任务中创建 CompletableFuture。然后我们对这个 future 应用“exceptionally”方法,然后是“theAccept”方法。我们不会将 Accept 方法返回的新 future 分配给任何变量。然后我们在原始 future 调用“加入”。我们看到的是“exceptionally”方法和“thenAccept”都被调用了。我们看到它是因为他们在输出中打印了适当的行。但是异常并没有被“异常”方法抑制。在这种情况下,抑制异常并为我们提供一些默认值正是我们对“异常”的期望。

在第二个片段中,我们做几乎相同的事情,但将新返回的 future 分配给变量并在其上调用“join”。在这种情况下,正如预期的那样,异常被抑制了。

从我的角度来看,对于第一部分,一致的行为要么不抑制异常并且不调用“exceptionally”和“thenAccept”,要么异常调用并抑制异常。

为什么我们要介于两者之间?

第一个片段:

public class TestClass {
    public static void main(String[] args) {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(TestClass::doSomethingForInteger);

        future.exceptionally(e -> {
                    System.out.println("Exceptionally");
                    return 42;
                })
                .thenAccept(r -> {
                    System.out.println("Accept");
                });

        future.join();
    }

    private static int doSomethingForInteger() {
        throw new IllegalArgumentException("Error");
    }
}

第二个片段:

public class TestClass {
    public static void main(String[] args) {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(TestClass::doSomethingForInteger);

        CompletableFuture<Void> voidCompletableFuture = future.exceptionally(e -> {
            System.out.println("Exceptionally");
            return 42;
        })
                .thenAccept(r -> {
                    System.out.println("Accept");
                });

        voidCompletableFuture.join();
    }

    private static int doSomethingForInteger() {
        throw new IllegalArgumentException("Error");
    }
}

最佳答案

没有“抑制异常”这样的东西。当您调用 exceptionally 时,您正在创建一个新的 future ,它将使用前一阶段的结果或如果前一阶段异常完成时评估函数的结果来完成。前一阶段,即您正在异常(exception)地调用的 future ,不受影响。

这适用于链接依赖函数或操作的所有方法。这些方法中的每一种都创建了一个新的 future ,它将按照记录完成。它们都不会影响您调用该方法的现有 future 。

也许,通过下面的例子会更清楚:

CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    return "a string";
});

CompletableFuture<Integer> f2 = f1.thenApply(s -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
    return s.length();
});

f2.thenAccept(i -> System.out.println("result of f2 = "+i));

String s = f1.join();
System.out.println("result of f1 = "+s);

ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);

在这里,应该清楚依赖阶段的结果,一个Integer,不能取代先决条件阶段的结果,一个String。这只是两种不同的 future ,有着不同的结果。由于在 f1 上调用 join() 查询第一阶段的结果,因此它不依赖于 f2,因此,不甚至等待它的完成。 (这也是代码在最后等待所有后台 Activity 结束的原因)。

exceptionally 的用法没有区别。在非异常(exception)情​​况下,下一个阶段具有相同的类型甚至相同的结果可能会令人困惑,但这不会改变存在两个不同阶段的事实。

static void report(String s, CompletableFuture<?> f) {
    f.whenComplete((i,t) -> {
        if(t != null) System.out.println(s+" completed exceptionally with "+t);
        else System.out.println(s+" completed with value "+i);
    });
}
CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    throw new IllegalArgumentException("Error for testing");
});
CompletableFuture<Integer> f2 = f1.exceptionally(t -> 42);

report("f1", f1);
report("f2", f2);

ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);

似乎人们普遍认为 CompletableFuture 链接方法是某种单一 future builder ,不幸的是这是误导性的错误。另一个陷阱是以下错误:

CompletableFuture<?> f = CompletableFuture.supplyAsync(() -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    System.out.println("initial stage");
    return "";
}).thenApply(s -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    System.out.println("second stage");
    return s;
}).thenApply(s -> {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    System.out.println("third stage");
    return s;
}).thenAccept(s -> {
    System.out.println("last stage");
});

f.cancel(true);
report("f", f);

ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);

如前所述,每个链接方法都会创建一个新阶段,因此保留对最后一个链接方法返回的阶段(即最后一个阶段)的引用适合获得最终结果。但是取消这个阶段只会取消最后一个阶段,而不会取消任何先决条件阶段。此外,在取消之后,最后一个阶段不再依赖于其他阶段,因为它已经通过取消完成并且能够报告此异常结果,而其他现在不相关的阶段仍在后台进行评估。

关于java - java.util.concurrent.CompletableFuture 中的异常传播,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54880333/

相关文章:

java - 如何在 CompletableFutures 中收集成功和错误?

Java 8 Completable Future - 并行执行

java - 具有弱引用的 ConcurrentHashmap

main 方法的 Java 习惯

java - 如何将数组列表值返回到单独的数组中?

java - 如何将 cloudhopper 演示作为 Java 应用程序运行?

java - shutdown() 和 shutdownNow() 的混合

java - 修改 ConcurrentHashMap 中的值

java - CompletableFuture 未在超时时完成

java - ActionBarSherlock 中的 SearchView 折叠动画