简体   繁体   English

CompletableFuture 异常处理 runAsync & thenRun

[英]CompletableFuture exception handling runAsync & thenRun

Let's say I have this sample code and an exception is encountered inside runAsync .假设我有这个示例代码,并且在runAsync中遇到了异常。 My question is would this exception prevent the thenRun from getting executed as thenRun runs in the same thread as the caller method of this code.我的问题是这个异常是否会阻止thenRun被执行,因为thenRun运行在与此代码的调用者方法相同的线程中。

private void caller() {
    CompletableFuture.runAsync(() -> {
          try {
              // some code
          } catch (Exception e) {
              throw new CustomException(errorMessage, e);
          }
         }, anInstanceOfTaskExecutor).thenRun(
         // thenRun code
     ));
}

I already went through this thread and it explains how you can handle exceptions thrown from asynchronous blocks (ie by blocking and using join ).我已经浏览过这个线程,它解释了如何处理异步块抛出的异常(即通过阻塞和使用join )。 I want to know if code inside thenRun block would be executed or not if CompletableFuture completesExceptionally .我想知道如果CompletableFuture completesExceptionallythenRun块中的代码是否会被执行。

Update :更新

I ran some code to test this:我运行了一些代码来测试这个:

CompletableFuture.runAsync(() -> {
      List<Integer> integerList = new ArrayList<>();
      integerList.get(1);    // throws exception
    }).thenRun(() -> {
      System.out.println("No exception occurred");
    });

It does not print anything and it means exception didn't 'propagate up to/reach' the caller method's thread from the asynchronous block.它不打印任何内容,这意味着异常没有从异步块“传播到/到达”调用方方法的线程。 I understand the expected behavior here now but I have following questions:我现在了解这里的预期行为,但我有以下问题:

  1. Why is it silently failing even though the CompletableFuture completesExceptionally?为什么即使 CompletableFuture 异常完成,它也会默默地失败?
  2. How does it work in the background?它是如何在后台工作的?
  3. Is it because both these threads (caller's thread & asynchronous thread) have their own stack space?是因为这两个线程(调用者线程和异步线程)都有自己的堆栈空间吗?

General Information一般信息

The documentation of CompletionStage explains the general rules of the interface: CompletionStage的文档解释了接口的一般规则:

A stage of a possibly asynchronous computation, that performs an action or computes a value when another CompletionStage completes.可能是异步计算的一个阶段,它在另一个CompletionStage完成时执行一个操作或计算一个值。 A stage completes upon termination of its computation, but this may in turn trigger other dependent stages.一个阶段在其计算终止时完成,但这可能反过来触发其他相关阶段。 The functionality defined in this interface takes only a few basic forms, which expand out to a larger set of methods to capture a range of usage styles:此接口中定义的功能仅采用几种基本形式,可扩展为更大的方法集以捕获一系列使用风格:

  • The computation performed by a stage may be expressed as a Function , Consumer , or Runnable (using methods with names including apply , accept , or run , respectively) depending on whether it requires arguments and/or produces results.阶段执行的计算可以表示为FunctionConsumerRunnable (分别使用名称包括applyacceptrun的方法),具体取决于它是否需要参数和/或产生结果。 For example:例如:

     stage.thenApply(x -> square(x)) .thenAccept(x -> System.out.print(x)) .thenRun(() -> System.out.println());

An additional form ( compose ) allows the construction of computation pipelines from functions returning completion stages.另一种形式( compose )允许从返回完成阶段的函数构造计算管道。

Any argument to a stage's computation is the outcome of a triggering stage's computation.阶段计算的任何参数都是触发阶段计算的结果。

  • One stage's execution may be triggered by completion of a single stage, or both of two stages, or either of two stages.一个阶段的执行可以由单个阶段的完成触发,也可以由两个阶段的完成触发,或者两个阶段中的任一个触发。 Dependencies on a single stage are arranged using methods with prefix then .使用带有前缀then的方法排列单个阶段的依赖关系。 Those triggered by completion of both of two stages may combine their results or effects, using correspondingly named methods.由两个阶段完成触发的那些可以使用相应命名的方法组合它们的结果或效果。 Those triggered by either of two stages make no guarantees about which of the results or effects are used for the dependent stage's computation.由两个阶段中的任何一个触发的那些不保证哪些结果或效果用于从属阶段的计算。

  • Dependencies among stages control the triggering of computations, but do not otherwise guarantee any particular ordering.阶段之间的依赖关系控制计算的触发,但不保证任何特定的顺序。 Additionally, execution of a new stage's computations may be arranged in any of three ways: default execution, default asynchronous execution (using methods with suffix async that employ the stage's default asynchronous execution facility), or custom (via a supplied Executor ).此外,新阶段计算的执行可以通过以下三种方式中的任何一种进行安排:默认执行、默认异步执行(使用带有async后缀的方法,该方法采用阶段的默认异步执行工具)或自定义(通过提供的Executor )。 The execution properties of default and async modes are specified by CompletionStage implementations, not this interface.默认和异步模式的执行属性由CompletionStage实现指定,而不是此接口。 Methods with explicit Executor arguments may have arbitrary execution properties, and might not even support concurrent execution, but are arranged for processing in a way that accommodates asynchrony.具有显式Executor参数的方法可能具有任意执行属性,甚至可能不支持并发执行,但以适应异步的方式安排处理。

  • Two method forms ( handle and whenComplete ) support unconditional computation whether the triggering stage completed normally or exceptionally.两种方法形式( handlewhenComplete )支持无条件计算触发阶段是否正常完成或异常完成。 Method exceptionally supports computation only when the triggering stage completes exceptionally, computing a replacement result, similarly to the java [sic] catch keyword.方法仅在触发阶段异常完成时才exceptionally支持计算,计算替换结果,类似于 java [sic] catch关键字。 In all other cases, if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause.在所有其他情况下,如果一个阶段的计算因(未经检查的)异常或错误而突然终止,则所有需要其完成的相关阶段也会异常完成,并且CompletionException将异常作为其原因。 If a stage is dependent on both of two stages, and both complete exceptionally, then the CompletionException may correspond to either one of these exceptions.如果一个阶段依赖于两个阶段,并且都异常完成,那么CompletionException可能对应于这些异常中的任何一个。 If a stage is dependent on either of two others, and only one of them completes exceptionally, no guarantees are made about whether the dependent stage completes normally or exceptionally.如果一个阶段依赖于其他两个阶段中的任何一个,并且其中只有一个异常完成,则无法保证依赖阶段是正常完成还是异常完成。 In the case of method whenComplete , when the supplied action itself encounters an exception, then the stage completes exceptionally with this exception unless the source stage also completed exceptionally, in which case the exceptional completion from the source stage is given preference and propagated to the dependent stage.在方法whenComplete的情况下,当提供的操作本身遇到异常时,除非源阶段也异常完成,否则该阶段将异常完成,除非源阶段也异常完成,在这种情况下,源阶段的异常完成优先并传播到依赖项阶段。

All methods adhere to the above triggering, execution, and exceptional completion specifications (which are not repeated in individual method specifications).所有方法都遵循上述触发、执行和异常完成规范(在单个方法规范中不再重复)。 [...] [...]

[...] [...]

And documentation of CompletableFuture explains the threading rules (and other policies) where, as documented above, some of which are left up to the implementations of CompletionStage : CompletableFuture的文档解释了线程规则(和其他策略),如上所述,其中一些由CompletionStage的实现决定:

A Future that may be explicitly completed (setting its value and status), and may be used as a CompletionStage , supporting dependent functions and actions that trigger upon its completion.可以显式完成的Future (设置其值和状态),并且可以用作CompletionStage ,支持在完成时触发的依赖函数和操作。

When two or more threads attempt to complete , completeExceptionally , or cancel a CompletableFuture , only one of them succeeds.当两个或多个线程尝试completecompleteExceptionallycancel CompletableFuture时,只有其中一个成功。

In addition to these and related methods for directly manipulating status and results, CompletableFuture implements interface CompletionStage with the following policies:除了直接操作状态和结果的这些和相关方法之外, CompletableFuture还使用以下策略实现接口CompletionStage

  • Actions supplied for dependent completions of non-async methods may be performed by the thread that completes the current CompletableFuture , or by any other caller of a completion method.为非异步方法的依赖完成提供的操作可以由完成当前CompletableFuture的线程执行,或者由完成方法的任何其他调用者执行。

  • All async methods without an explicit Executor argument are performed using the ForkJoinPool.commonPool() (unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run each task).所有没有显式Executor参数的异步方法都使用ForkJoinPool.commonPool()执行(除非它不支持至少两个并行级别,在这种情况下,会创建一个新Thread来运行每个任务)。 This may be overridden for non-static methods in subclasses by defining method defaultExecutor() .这可以通过定义方法defaultExecutor()来覆盖子类中的非静态方法。 To simplify monitoring, debugging, and tracking, all generated asynchronous tasks are instances of the marker interface CompletableFuture.AsynchronousCompletionTask .为了简化监控、调试和跟踪,所有生成的异步任务都是标记接口CompletableFuture.AsynchronousCompletionTask的实例。 Operations with time-delays can use adapter methods defined in this class, for example: supplyAsync(supplier, delayedExecutor(timeout, timeUnit)) .具有时间延迟的操作可以使用此类中定义的适配器方法,例如: supplyAsync(supplier, delayedExecutor(timeout, timeUnit)) To support methods with delays and timeouts, this class maintains at most one daemon thread for triggering and cancelling actions, not for running them.为了支持具有延迟和超时的方法,该类最多维护一个用于触发和取消操作的守护线程,而不是用于运行它们。

  • All CompletionStage methods are implemented independently of other public methods, so the behavior of one method is not impacted by overrides of others in subclasses.所有CompletionStage方法都是独立于其他公共方法实现的,因此一个方法的行为不会受到子类中其他方法的覆盖的影响。

  • All CompletionStage methods return CompletableFuture s.所有CompletionStage方法都返回CompletableFuture To restrict usages to only those methods defined in interface CompletionStage , use method minimalCompletionStage() .要将使用限制为仅在接口CompletionStage中定义的那些方法,请使用方法minimalCompletionStage() Or to ensure only that clients do not themselves modify a future, use method copy() .或者为了确保客户自己不会修改未来,请使用方法copy()

CompletableFuture also implements Future with the following policies: CompletableFuture还使用以下策略实现Future

  • Since (unlike FutureTask ) this class has no direct control over the computation that causes it to be completed, cancellation is treated as just another form of exceptional completion.由于(与FutureTask不同)此类无法直接控制导致其完成的计算,因此取消被视为另一种形式的异常完成。 Method cancel has the same effect as completeExceptionally(new CancellationException()) .方法cancelcompleteExceptionally(new CancellationException())具有相同的效果。 Method isCompletedExceptionally() can be used to determine if a CompletableFuture completed in any exceptional fashion.方法isCompletedExceptionally()可用于确定CompletableFuture是否以任何异常方式完成。

  • In case of exceptional completion with a CompletionException , methods get() and get(long, TimeUnit) throw an ExecutionException with the same cause as held in the corresponding CompletionException .如果使用CompletionException异常完成,方法get()get(long, TimeUnit)会抛出一个ExecutionException ,原因与对应的CompletionException中的原因相同。 To simplify usage in most contexts, this class also defines methods join() and getNow(T) that instead throw the CompletionException directly in these cases.为了在大多数情况下简化使用,此类还定义了join()getNow(T)方法,它们在这些情况下直接抛出CompletionException

[...] [...]


Your Questions你的问题

Here's your example code:这是您的示例代码:

CompletableFuture.runAsync(() -> {
      List<Integer> integerList = new ArrayList<>();
      integerList.get(1);    // throws exception
    }).thenRun(() -> {
      System.out.println("No exception occurred");
    });

If you aren't aware, methods such as thenRun return a new CompletionStage .如果您不知道,诸如thenRun类的方法会返回一个CompletionStage So your code is similar to the following:因此,您的代码类似于以下内容:

CompletableFuture<Void> runAsyncStage = CompletableFuture.runAsync(() -> List.of().get(0));
CompletableFuture<Void> thenRunStage =
    runAsyncStage.thenRun(() -> System.out.println("thenRun executing!"));

The thenRunStage is triggered by the completion of the runAsyncStage which, in this case, is guaranteed to complete exceptionally with an IndexOutOfBoundsException . thenRunStagerunAsyncStage的完成触发,在这种情况下,它保证异常完成并带有IndexOutOfBoundsException As for why the Runnable isn't executed, that's due to the contract of CompletionStage#thenRun(Runnable) :至于为什么Runnable没有被执行,那是由于CompletionStage#thenRun(Runnable)的合同:

Returns a new CompletionStage that, when this stage completes normally, executes the given action.返回一个新的CompletionStage ,当此阶段正常完成时,执行给定的操作。 See the CompletionStage documentation for rules covering exceptional completion.有关异常完成的规则​​,请参阅CompletionStage文档。

Due to the triggering stage completing exceptionally the thenRunStage stage also completes exceptionally, which means the Runnable is skipped.由于触发阶段异常完成, thenRunStage阶段也异常完成,这意味着Runnable被跳过。

1. "Why is it silently failing even though the CompletableFuture completesExceptionally?" 1.“为什么即使 CompletableFuture 异常完成,它也会默默地失败?”

The example code is the equivalent of swallowing an exception with a try-catch block.示例代码相当于使用 try-catch 块吞下异常。 You don't see the exception because you haven't written code that would report the exception.您没有看到异常,因为您还没有编写会报告异常的代码。 Both the runAsyncStage and thenRunStage stages have completed exceptionally, the latter because of the former completing exceptionally. runAsyncStagethenRunStage阶段都异常完成,后者是因为前者异常完成。

If you want to be aware of the exception "within the chain" of stages then you have to use stages such as exceptionally[Async] , handle[Async] , and whenComplete[Async] .如果您想了解阶段“链内”的异常,则必须使用阶段,例如exceptionally[Async]handle[Async]whenComplete[Async] Doing it this way allows you to change the behavior of the chain based on normal or exceptional completion of a trigger stage.这样做可以让您根据触发阶段的正常或异常完成来更改链的行为。

If you want to be aware of the exception "outside the chain" of stages then you have to use methods such as join() , get() , and get(long,TimeUnit) .如果您想了解阶段“链外”的异常,则必须使用join()get()get(long,TimeUnit)等方法。 If the stage had completed exceptionally then the first will throw a CompletionException wrapping the cause-of-failure while the latter two will throw an ExecutionException wrapping the cause-of-failure.如果阶段异常完成,则第一个阶段将抛出一个包含失败原因的CompletionException ,而后两个阶段将抛出一个包含失败原因的ExecutionException

2. "How does it work in the background?" 2.“它在后台是如何工作的?”

The implementation of CompletableFuture is too complex to explain in a Stack Overflow answer. CompletableFuture的实现过于复杂,无法在 Stack Overflow 答案中进行解释。 If you want to study the implementation you can look at the source code.如果您想研究实现,可以查看源代码。 Your JDK should have come with a src.zip file containing the Java source files.您的 JDK 应该带有一个包含 Java 源文件的src.zip文件。 You can also look at the source code online in the OpenJDK repositories .您还可以在线查看OpenJDK 存储库中的源代码。 For instance, here's the source code of CompletableFuture :例如,这是CompletableFuture的源代码:

https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java

3. "Is it because both these threads (caller's thread & asynchronous thread) have their own stack space?" 3.“是不是因为这两个线程(调用者线程和异步线程)都有自己的堆栈空间?”

One thread will not be aware of an exception in another thread unless there's some sort of communication between the two threads.除非两个线程之间存在某种通信,否则一个线程不会意识到另一个线程中的异常。 Calling methods such as join() will, when appropriate, communicate the exception to the calling thread which will throw said exception.调用方法如join()将在适当时将异常传达给将抛出所述异常的调用线程。 However, as shown by the answer to your first question, it's slightly more complicated than that.但是,如您的第一个问题的答案所示,它比这稍微复杂一些。 Even when a thread throws an exception within a single stage you won't see a stack trace or anything similar.即使线程在单个阶段内抛出异常,您也不会看到堆栈跟踪或类似的东西。 This is because the exception is caught and the stage is marked as failed with that exception as the cause .这是因为异常被捕获并且阶段被标记为失败,该异常作为原因 Other code then has to explicitly retrieve and handle that exception, as needed.然后,其他代码必须根据需要显式检索和处理该异常。

This is no different than using an ExecutorService and returned Future objects.这与使用ExecutorService并返回Future对象没有什么不同。 The task may fail in the background, but other code won't be aware of that until the Future is queried.该任务可能在后台失败,但在查询Future之前,其他代码不会意识到这一点。

From bounty: “I am looking to understand the details of how threads interact with each other.”来自赏金:“我希望了解线程如何相互交互的细节。”

I'm not sure what else to add.我不确定还要添加什么。 The CompletionStage API is an abstraction "above" threads. CompletionStage API 是“高于”线程的抽象。 You simply tell the API how you would like the chain of commands to execute, including which thread pools to use for each stage, and the implementation handles all the inter-thread communication for you.您只需告诉 API 您希望如何执行命令链,包括每个阶段使用哪些线程池,并且实现为您处理所有线程间通信。 That said, each thread does it's own thing, it's just the API is designed to provide an easier and reactive way to communicate between threads.也就是说,每个线程都做自己的事情,只是 API 旨在提供一种更简单和反应性的方式在线程之间进行通信。 If you're interested in how that's implemented then I recommend studying the source code (linked above).如果您对它的实现方式感兴趣,那么我建议您研究源代码(上面链接)。

It will depend on which step you are adding exceptionally .这将取决于您exceptionally添加的步骤。

In the below case, it will skip thenRun and directly execute exceptionally block.在下面的情况下,它将跳过thenRun并直接执行异常块。

CompletableFuture.runAsync(() -> { 
     //process and throw exception
     }, anInstanceOfTaskExecutor )
    .thenRun(() -> {})
    .exceptionally(exception -> {
      // do something, handle exception
    })
 ));

In this case, it will execute thenRun .在这种情况下,它将执行thenRun

   CompletableFuture.runAsync(() -> { 
     //process and throw exception
     }, anInstanceOfTaskExecutor )
    .exceptionally(exception -> {
      // do something, handle exception
    })
    .thenRun(() -> {})
 ));

I hope, it helps我希望,它有帮助

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM