Java CompletableFuture 吞下异常?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24183246/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 10:39:53  来源:igfitidea点击:

CompletableFuture swallows exceptions?

javaexceptionjava-8

提问by maciej

I've been playing around with CompletableFutureand noticed a strange thing.

我一直在玩,CompletableFuture并注意到一件奇怪的事情。

String url = "http://google.com";

CompletableFuture<String> contentsCF = readPageCF(url);
CompletableFuture<List<String>> linksCF = contentsCF.thenApply(_4_CompletableFutures::getLinks);

linksCF.thenAccept(list -> {
    assertThat(list, not(empty()));
});

linksCF.get();

If, in my thenAcceptcall, the assertion fails, the exception is not propagated. I tried something even uglier then:

如果在我的thenAccept调用中,断言失败,则不会传播异常。然后我尝试了更丑陋的东西:

linksCF.thenAccept(list -> {
    String a = null;
    System.out.println(a.toString());
});

nothing happens, no exception is propagated. I tried using methods like handleand others related to exceptions in CompletableFutures, but failed - none is propagating the exception as expected.

什么都没有发生,也没有传播异常。我尝试使用handle与 中的异常相关的方法和其他方法CompletableFutures,但失败了 - 没有人按预期传播异常。

When I debugged the CompletableFuture, it does catch the exception like this:

当我调试 时CompletableFuture,它确实捕获了这样的异常:

final void internalComplete(T v, Throwable ex) {
    if (result == null)
        UNSAFE.compareAndSwapObject
            (this, RESULT, null,
             (ex == null) ? (v == null) ? NIL : v :
             new AltResult((ex instanceof CompletionException) ? ex :
                           new CompletionException(ex)));
    postComplete(); // help out even if not triggered
}

and nothing else.

没有别的。

I'm on JDK 1.8.0_05 x64, Windows 7.

我使用的是 JDK 1.8.0_05 x64,Windows 7。

Am I missing something here?

我在这里错过了什么吗?

回答by Gregor Koukkoullis

The problem is you never request to receive the results of your call to linksCF.thenAccept(..).

问题是您从不要求接收您对 的调用结果linksCF.thenAccept(..)

Your call to linksCF.get()will wait for the results of the execution in your chain. But it will only return the results of then linksCF future. This doesn't include the results of your assertion.

您的调用linksCF.get()将等待链中的执行结果。但它只会返回 then linksCF 未来的结果。这不包括您的断言结果。

linksCF.thenAccept(..)will return a new CompletableFuture instance. To get the exception thrown call get()or check the exception status with isCompletedExceptionally()on the newly return CompletableFuture instance.

linksCF.thenAccept(..)将返回一个新的 CompletableFuture 实例。在新返回的 CompletableFuture 实例上获取抛出的异常调用get()或检查异常状态isCompletedExceptionally()

CompletableFuture<Void> acceptedCF = linksCF.thenAccept(list -> {
    assertThat(list, not(empty()));
});

acceptedCF.exceptionally(th -> {
    // will be executed when there is an exception.
    System.out.println(th);
    return null;
});
acceptedCF.get(); // will throw ExecutionException once results are available

Alternative?

选择?

CompletableFuture<List<String>> appliedCF = linksCF.thenApply(list -> {
    assertThat(list, not(empty()));
    return list;
});

appliedCF.exceptionally(th -> {
    // will be executed when there is an exception.
    System.out.println(th);
    return Coolections.emptyList();
});
appliedCF.get(); // will throw ExecutionException once results are available

回答by Marco13

Although the question is basically already answered by Gregor Koukkoullis (+1), here is a MCVEthat I created to test this.

尽管 Gregor Koukkoullis (+1) 基本上已经回答了这个问题,但我创建了一个MCVE来测试这个问题。

There are several options for obtaining the actual exception that caused the problem internally. However, I don't see why calling geton the future that is returned by thenAcceptshould be an issue. In doubt, you could also use thenApplywith the identity function and use a nice fluent pattern, like in

有多种方法可用于获取在内部导致问题的实际异常。但是,我不明白为什么调用get返回的未来thenAccept应该是一个问题。有疑问,你也可以使用thenApplyidentity 函数并使用一个很好的流畅模式,比如

List<String> list = 
    readPage().
    thenApply(CompletableFutureTest::getLinks).
    thenApply(t -> {
        // check assertion here
        return t;
    }).get();

But maybe there's a particular reason why you want to avoid this.

但也许你想避免这种情况有一个特殊的原因。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;

public class CompletableFutureTest
{
    public static void main(String[] args) 
        throws InterruptedException, ExecutionException
    {
        CompletableFuture<String> contentsCF = readPage();
        CompletableFuture<List<String>> linksCF = 
            contentsCF.thenApply(CompletableFutureTest::getLinks);

        CompletableFuture<Void> completionStage = linksCF.thenAccept(list -> 
        {
            String a = null;
            System.out.println(a.toString());
        });        

        // This will NOT cause an exception to be thrown, because
        // the part that was passed to "thenAccept" will NOT be
        // evaluated (it will be executed, but the exception will
        // not show up)
        List<String> result = linksCF.get();
        System.out.println("Got "+result);


        // This will cause the exception to be thrown and
        // wrapped into an ExecutionException. The cause
        // of this ExecutionException can be obtained:
        try
        {
            completionStage.get();
        }
        catch (ExecutionException e)
        {
            System.out.println("Caught "+e);
            Throwable cause = e.getCause();
            System.out.println("cause: "+cause);
        }

        // Alternatively, the exception may be handled by
        // the future directly:
        completionStage.exceptionally(e -> 
        { 
            System.out.println("Future exceptionally finished: "+e);
            return null; 
        });

        try
        {
            completionStage.get();
        }
        catch (Throwable t)
        {
            System.out.println("Already handled by the future "+t);
        }

    }

    private static List<String> getLinks(String s)
    {
        System.out.println("Getting links...");
        List<String> links = new ArrayList<String>();
        for (int i=0; i<10; i++)
        {
            links.add("link"+i);
        }
        dummySleep(1000);
        return links;
    }

    private static CompletableFuture<String> readPage()
    {
        return CompletableFuture.supplyAsync(new Supplier<String>() 
        {
            @Override
            public String get() 
            {
                System.out.println("Getting page...");
                dummySleep(1000);
                return "page";
            }
        });
    }

    private static void dummySleep(int ms)
    {
        try
        {
            Thread.sleep(ms);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
    }
}

回答by Mike Strobel

If, in my thenAccept call, the assertion fails, the exception is not propagated.

如果在我的 thenAccept 调用中,断言失败,则不会传播异常。

The continuation that you register with thenAccept()is a separate task from the linksCFfuture. The linksCFtask completed successfully; there is no error for it to report. It has its final value. An exception thrown by linksCFshould only indicate a problem producing the result of linksCF; if some other piece of code that consumes the result throws, that does not indicate a failure to produce the result.

您注册的延续是与未来thenAccept()不同的任务linksCF。该linksCF任务圆满完成; 它没有错误报告。它有它的最终价值。抛出的异常linksCF应该只指示产生结果的问题linksCF;如果其他一些消耗结果的代码抛出,这并不表示无法产生结果。

To observe an exception that happens in a continuation, you must observe the CompletableFutureof the continuation.

要观察延续中发生的异常,您必须观察CompletableFuture延续的 。

correct. but 1) I should not be forced to call get() - one of the points of the new constructs; 2) it's wrapped in an ExecutionException

正确的。但是 1) 我不应该被迫调用 get() - 新构造的要点之一;2) 它被包裹在一个 ExecutionException 中

What if you wanted to hand the result off to multiple, independent continuations using thenAccept()? If one of those continuations were to throw, why should that impact the parent, or the other continuations?

如果您想使用 将结果交给多个独立的延续thenAccept()怎么办?如果这些延续中的一个要抛出,为什么会影响父级或其他延续?

If you want to treat linksCFas a node in a chain and observe the result (and any exceptions) that happen within the chain, then you should call get()on the last link in the chain.

如果您想将其linksCF视为链中的一个节点并观察链中发生的结果(以及任何异常),那么您应该调用get()链中的最后一个链接。

You can avoid the checked ExecutionExceptionby using join()instead of get(), which will wrap the error in an unchecked CompletionException(but it is still wrapped).

您可以ExecutionException通过使用join()代替来避免检查get(),这会将错误包装在未检查中CompletionException(但它仍然被包装)。

回答by pdem

The answers here helped me to manage exception in CompletableFuture, using "exceptionnaly"method, but it missed a basic example, so here is one, inspired from Marco13 answer:

这里的答案帮助我管理 CompletableFuture 中的异常,使用“exceptionnaly”方法,但它错过了一个基本示例,所以这里有一个,灵感来自 Marco13 答案:

/**
 * Make a future launch an exception in the accept.
 *
 * This will simulate:
 *  - a readPage service called asynchronously that return a String after 1 second
 *  - a call to that service that uses the result then throw (eventually) an exception, to be processed by the exceptionnaly method.
 *
 */
public class CompletableFutureTest2
{
    public static void main(String[] args)
        throws InterruptedException, ExecutionException
    {
        CompletableFuture<String> future = readPage();

        CompletableFuture<Void> future2 = future.thenAccept(page->{
            System.out.println(page);
            throw new IllegalArgumentException("unexpected exception");
        });

        future2.exceptionally(e->{
          e.printStackTrace(System.err);
          return null;
        });

    }

    private static CompletableFuture<String> readPage()
    {

      CompletableFuture<String> future = new CompletableFuture<>();
      new Thread(()->{
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
        }

        // FUTURE: normal process
        future.complete("page");

      }).start();
        return future;
    }


}

The mistake to avoid is to call "exceptionnaly" on the 1st future (the variable future in my code) instead of the future returned by the "thenAccept" which contains the lambda that may throw an exception (the variable future2 in my code). .

要避免的错误是在第一个未来(我的代码中的变量 future)上调用“exceptionnaly”,而不是“thenAccept”返回的未来,它包含可能引发异常的 lambda(我的代码中的变量 future2)。.

回答by jeffery

As usual, understanding the behavior of CompletableFutureis better left to the official docs and a blog.

像往常一样,了解 的行为CompletableFuture最好留给官方文档和博客。

Each then...()chaining method of the CompletableFutureclass, which implements CompletionStage, accepts a an argument a CompletionStage. The stage that is passed depends on which order of then...()methods you've chained. Again, docs, but here's that aforementioned blog.

实现CompletionStagethen...()CompletableFuture类的每个链接方法都接受一个参数 a 。传递的阶段取决于您链接的方法的顺序。同样,文档,但这里是前面提到的博客CompletionStagethen...()