java.util.concurrent.CompletableFuture中的异常传播 [英] Exception propagation in java.util.concurrent.CompletableFuture

查看:137
本文介绍了java.util.concurrent.CompletableFuture中的异常传播的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有两个代码段.

在第一个中,我们从任务中创建CompletableFuture,该任务总是会引发一些异常.然后,我们将异常"方法应用于此未来,然后应用"theAccept"方法.我们不会将Accept方法返回的新的Future赋给任何变量.然后我们在原始将来调用"join".我们看到的是,已经调用了异常"方法以及"thenAccept"方法.我们看到它是因为他们在输出中打印了适当的行.但是Exception尚未通过"exceptionally"方法进行抑制.在这种情况下,禁止异常并为我们提供一些默认值,这正是我们从例外情况"中获得的期望.

在第二个片段中,我们几乎执行相同的操作,但是将新返回的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");
    }
}

解决方案

没有抑制异常"之类的东西.调用exceptionally时,您正在创建一个新的Future,如果前一阶段异常完成,则将以上一阶段的结果或函数评估的结果来完成.上一个阶段,即将来调用exceptionally的阶段,不会受到影响.

这适用于链接依赖函数或动作的所有所有方法.这些方法中的每一个都创造了一个新的未来,它将如所记载的那样完成.它们都不会影响调用该方法的现有未来.

也许,通过以下示例,它会变得更加清晰:

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.这仅仅是两个不同的未来,其结果也不尽相同.而且,由于在f1上调用join()会查询第一阶段的结果,因此它不依赖于f2,因此甚至不必等待其完成. (这也是代码在所有后台活动结束时都等待结束的原因.)

exceptionally的用法没有什么不同.在非例外情​​况下,下一个阶段具有相同的类型甚至是相同的结果,这可能会令人困惑,但这并不能改变存在两个不同阶段的事实.

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链接方法似乎有广泛的思维方式,可以成为单个未来的构建者,但不幸的是,这是误导性的错误.另一个陷阱是以下错误:

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

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

There are two snippets of code.

In the first one we create the CompletableFuture from the task which always throws some exception. Then we apply "exceptionally" method to this future, then "theAccept" method. We DO NOT assign new future returned by theAccept method to any variable. Then we invoke "join" on original future. What we see is that "exceptionally" method has been invoked as well as the "thenAccept". We see It because they printed appropriate lines in output. But the Exception has not been suppressed by "exceptionally" method. Suppress exception and provide us with some default value instead is exactly what we expected from "exceptionally" in this case.

In second snippet we do almost the same but assign new returned future to variable and invoke "join" on It. In this case as expected exception is suppressed.

From my point of view for the first part the consistent behavior is either not to suppress exception and not to invoke "exceptionally" and "thenAccept" or call exceptionally and suppress exception.

Why do we have something in between?

First snippet:

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

Second snippet:

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

解决方案

There is no such thing as "suppressing an exception". When you invoke exceptionally, you are creating a new future, which will be completed with the result of the previous stage or the result of evaluating the function if the previous stage completed exceptionally. The previous stage, i.e. the future you’re invoking exceptionally on, is not affected.

This applies to all methods chaining a depend function or action. Each of these methods creates a new future, which will be completed as documented. None of them affects the existing future you’re invoking the method on.

Perhaps, it becomes much clearer with the following example:

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

Here, it should be clear that the result of the dependent stage, an Integer, can’t supersede the result of the prerequisite stage, a String. These simply are two different futures with different results. And since calling join() on f1 queries for the result of the first stage, it isn’t dependent on f2 and hence, does not even wait for its completion. (That’s also the reason why the code waits for the end of all background activity at the end).

The usage of exceptionally is not different. It might be confusing that the next stage has the same type and even the same result in the non-exceptional case, but it doesn’t change the fact that there are two distinct stages.

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

There seems to be a widespread mindset of the CompletableFuture chaining methods to be some kind of builder for a single future, which unfortunately is misleadingly wrong. Another pitfall is the following mistake:

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

As explained, each chained method creates a new stage, so keeping a reference to the stage returned by the last chained method, i.e. the last stage, is suitable to get the final result. But canceling this stage will only cancel that last stage and none of the prerequisite stages. Also, after cancellation, the last stage does not depend on the other stages anymore, as it is already completed by cancellation and capable of reporting this exceptional result while the other, now unrelated stages are still being evaluated in the background.

这篇关于java.util.concurrent.CompletableFuture中的异常传播的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆