当前位置: 首页 > 知识库问答 >
问题:

Java.util.concurrent.CompletableFuture 中的 Exception propagation

漆雕彦
2023-03-14

有两个代码片段。

在第一个例子中,我们从总是引发一些异常的任务中创建CompletableFuture。然后我们将“例外”方法应用于该未来,然后是“接受”方法。我们不会将Accept方法返回的新future分配给任何变量。然后我们对原始未来调用“join”。我们看到的是,“异常”方法以及“thenAccept”都已被调用。我们看到它是因为他们在输出中打印了适当的行。但“异常”方法并没有抑制该异常。在这种情况下,抑制异常并为我们提供一些默认值正是我们期望的“异常”。

在第二段代码中,我们做了几乎相同的事情,但是将新返回的future赋给变量,并对其调用“join”。在这种情况下,如预期的异常被抑制。

从我的角度来看,第一部分,一致的行为要么是不抑制异常,要么不调用“异常”和“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");
    }
}

共有1个答案

龚永新
2023-03-14

不存在“抑制异常”这样的事情。当您异常调用<code>时,您正在创建一个新的未来,该未来将使用前一阶段的结果或前一阶段异常完成时的函数评估结果来完成。前一阶段,即您在异常情况下调用<code>的未来</code>不会受到影响。

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

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

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

这里应该清楚的是,从属阶段的结果(一个< code >整数)不能取代先决阶段的结果(一个< code >字符串)。这只是两种不同的未来,会有不同的结果。由于在< code>f1上调用< code>join()查询第一阶段的结果,它不依赖于< code>f2,因此甚至不等待其完成。(这也是代码在最后等待所有后台活动结束的原因)。

< code > exceptually 的用法没有什么不同。在非异常情况下,下一个阶段具有相同的类型,甚至相同的结果,这可能会令人困惑,但这不会改变有两个不同阶段的事实。

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

人们似乎普遍认为< code>CompletableFuture链接方法是某种单一未来的构建器,但不幸的是,这是错误的。另一个陷阱是以下错误:

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

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

 类似资料:
  • 我使用子JPanel构造JScrollPane,然后尝试将JScrollPane添加到父JPanel中,希望有一个可滚动的自定义JPanel。 我有一个大的细白线前面的所有我的组件,他们是没有滚动。有什么想法吗?

  • 我已经为android Studio创建了一个webview应用程序。但没有加载web URL。错误为NET::ERR_ACCESS_DENIED。有谁能帮忙吗

  • 我有一个使用Java Version8的Spring和Maven的项目。当我运行maven update时,它适用于Java9或更高版本,当我编译它时,它适用于Java8。我注意到了这一点,因为用Maven更新会将一个特定的类更改为Java9,而我无法导入这个类。 这个类是:javax.annotation.generated(Java8)。 这是要导入的类。但是,当我进行maven更新时,文件的

  • 当应用程序启动EncryptionBootstrapConfiguration无法自动装配我的自定义TextEncryptor-https://github.com/spring-cloud/spring-cloud-commons/blob/cde7c7f3118382490c28776f66e0a56f248141fd/spring-cloud-context/src/main/java/or

  • 我有一个问题,找出我的源代码中的错误。 有什么办法可以解决它吗? 我的代码中抛出了“ArrayIndexOutOfBoundsException”,但我无法找到它的位置。 android studio中的logcat:

  • RISC-V 与中断相关的寄存器和指令 [info] 回顾:RISC-V 中的机器态(Machine Mode,机器模式,M 模式) 是 RISC-V 中的最高权限模式,一些底层操作的指令只能由机器态进行使用。 是所有标准 RISC-V 处理器都必须实现的模式。 默认所有中断实际上是交给机器态处理的,但是为了实现更多功能,机器态会将某些中断交由内核态处理。这些异常也正是我们编写操作系统所需要实现的