简体   繁体   中英

CompletableFuture exception behavior with join() then get()

My intuition is that the following code is wrong. I believe because join() is being used, any exceptions throw while completing the futures will be unchecked. Then when get() is called, there will be no checked exceptions, no logging of any errors, and difficulty diagnosing errors during failure.

    List<CompletableFuture> list = ImmutableList.of(future1, future2);
    CompletableFuture.allOf(list.toArray(new CompletableFuture[list.size()])).join();

    try {
        result1 = future1.get();
        result2 = future2.get();

    } catch (InterruptedException | ExecutionException e) {
        // will this ever run if join() is already called?
    }

I have looked through the documentation for CompletableFuture but haven't found the exact answer to my question. I am asking here and will then go read through the source code.

The only why I can see that the catch block code would run is if somehow checked exceptions can be saved in some execution context and not thrown in join() (or thrown wrapped by an unchecked exception), and then throw again in some form after get(). This seems unlikely to me.

So my ultimate question is, will the catch block code ever run?

Both the join and the get method are blocking method that relies on completion signals and returns the result T . Processing the piece of code as in question :-

On one hand, InterruptedException could be thrown while the thread is interrupted in the process of waiting as we do a get , the wait here is already completed by the join method.

Also, as stated in the join method documentation

/**
 * ... if a
 * computation involved in the completion of this
 * CompletableFuture threw an exception, this method throws an
 * (unchecked) {@link CompletionException} with the underlying
 * exception as its cause.
 */

So, on the other hand, the ExecutionException for futureN.get() in your case could only be thrown when and if the future completed exceptionally. Since the future if executed exceptionally would end up in throwing a CompletionException for the join call, it wouldn't reach the catch block ever or for that sake try block either.

Yes, the code would never be reached, but that doesn't make the "code wrong".

First, let's just try it out...

    CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
        throw new IllegalArgumentException();
    });
    try
    {
        CompletableFuture.allOf(future1).join();
    }
    catch (Exception e1)
    {
        System.out.println("I'd exit here."); // *1
    }

    try
    {
        future1.get();
    }
    catch (InterruptedException | ExecutionException e)
    {
        System.out.println("Entered!");
    }

Since you didn't do the try/catch "*1", the Exception would cause the method to exit and the get() would never be reached; so the second catch clause would never be executed.

However, the catch is still necessary because it's for the compiler, which has no way of knowing the previous call sequence.

The more straightforward way of doing this would be like this anyway:

    CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
        throw new IllegalArgumentException();
    });
    try
    {
        CompletableFuture.allOf(future1).join();
        future1.get();
    }
    catch (CompletionException e1) // this is unchecked, of course
    {
        System.out.println("Exception when joining");
    }
    catch (InterruptedException | ExecutionException e)
    {
        System.out.println("Exception when getting");
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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