异步编程CompletableFuture中异步计算结果触发回调

947 阅读5分钟

这是我参与8月更文挑战的第5天,活动详情查看: 8月更文挑战

CompletableFuture中对于异步创建的任务执行完成后,我们需要获取执行的结果,在执行过程中,抛出了异常,也可以获取到具体的异常信息,我们也可以根据不同的结果及异常信息完成接下来的操作。对于这样的编程需要,CompletableFuture同样也提供了方法让使用。

whenComplete(BiConsumer<? super T, ? super Throwable> action);

whenCompleteAsync( BiConsumer<? super T, ? super Throwable> action);

whenCompleteAsync( BiConsumer<? super T, ? super Throwable> action, Executor executor);

exceptionally( Function<Throwable, ? extends T> fn);

对于计算结果触发回调,也提供了四个方法,其中whenComplete、whenCompleteAsync 这三个方法看起来比较相试,接下来去查看一下的源码, 找到对应的区别

  • whenComplete(BiConsumer<? super T, ? super Throwable> action)
public CompletableFuture<T> whenComplete(
        BiConsumer<? super T, ? super Throwable> action) {
        return uniWhenCompleteStage(null, action);
    }
  •  whenCompleteAsync( BiConsumer<? super T, ? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(
        BiConsumer<? super T, ? super Throwable> action) {
        return uniWhenCompleteStage(asyncPool, action);
    }
  • whenCompleteAsync( BiConsumer<? super T, ? super Throwable> action, Executor executor)
 public CompletableFuture<T> whenCompleteAsync(
        BiConsumer<? super T, ? super Throwable> action, Executor executor) {
        return uniWhenCompleteStage(screenExecutor(executor), action);
    }

看这三个方法都内部调用了uniWhenCompleteStage方法,只是传入的线程池不一样,对于线程池的问题,在异步任务的创建的时候分析的比较详细,这里就不在进行分析了。

接下来我们对这三个方法使用来查看一下执行的结果

  • whenComplete
        ExecutorService executor = Executors.newFixedThreadPool(10);
        System.out.println(Thread.currentThread().getName() + ":main开始完毕");
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + ":子任务开始完毕");
                TimeUnit.SECONDS.sleep(3);
            System.out.println(Thread.currentThread().getName() + ":子任务执行完毕");
            return 123;
        }, executor);
        future.whenComplete((i, throwable) -> {
            System.out.println(Thread.currentThread().getName() + ":获取执行结果:" + i);
        });

        System.out.println(Thread.currentThread().getName() + ":main执行完毕")

查看执行结果:

main:main开始完毕
main:main执行完毕
pool-1-thread-1:子任务开始完毕
pool-1-thread-1:子任务执行完毕
pool-1-thread-1:获取执行结果:123

从打印的结果可以看出,我们获取结果的线程名与执行任务的线程名是一样的,说明whenComplete是在当前任务线程中继续执行指定的任务后续处理,接着我们来看whenCompleteAsync的处理

  • whenCompleteAsync
        ExecutorService executor = Executors.newFixedThreadPool(10);
        System.out.println(Thread.currentThread().getName() + ":main开始完毕");
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + ":子任务开始完毕");
                TimeUnit.SECONDS.sleep(3);
            System.out.println(Thread.currentThread().getName() + ":子任务执行完毕");
            return 123;
        }, executor);

        future.whenCompleteAsync((i, throwable) -> {
            System.out.println(Thread.currentThread().getName() + ":获取执行结果:" + i);
            executor.shutdown();
        });

        System.out.println(Thread.currentThread().getName() + ":main执行完毕");

查看执行结果:

main:main开始完毕
pool-1-thread-1:子任务开始完毕
main:main执行完毕
pool-1-thread-1:子任务执行完毕
ForkJoinPool.commonPool-worker-1:获取执行结果:123

Process finished with exit code 0

从打印的线程池名称来看whenCompleteAsync 中获取结果后续执行,是开启了一个新的线程来执行后续操作,由于没有传入自定义的线程,系统采用的是ForkJoinPool线程池来处理,并且我在获取到任务结果后,调用了 executor.shutdown();来关闭线程池,在这里就把线程池关闭,当结果执行完成后,整个应用程序也就关闭。 在获取结果的时候,有两个参数BiConsumer_<? super T, ? super Throwable> _action,BiConsumer 则是任务执行的时候返回的结果,在这里可以获取到,第二个参数则是在任务执行的时候,返回的异常信息,会传入到这个方法体,我们可以获取到具体的异常信息,然后对异常进行操作,上面的三个方法只是对于结果获取的任务采取的线程池不同而已,接下来就还剩下exceptionally这方法还没有分析,从方法的定义来看,只是获取异常的信息,老规矩,先贴源码,在一步一步分析具体的差异性信息

public CompletableFuture<T> exceptionally(
        Function<Throwable, ? extends T> fn) {
        return uniExceptionallyStage(fn);
    }

看这源码和前面三个函数差不多,只是没有传出执行的结果,好那我们就去使用它,看下除了返回值的不同还有没有其他的不同点

        ExecutorService executor = Executors.newFixedThreadPool(10);
        System.out.println(Thread.currentThread().getName() + ":main开始完毕");
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + ":子任务开始完毕");
                TimeUnit.SECONDS.sleep(3);
            int i = 1 / 0;
            System.out.println(Thread.currentThread().getName() + ":子任务执行完毕");
            return 123;
        }, executor);

        future.exceptionally(throwable -> {
            System.out.println(Thread.currentThread().getName() + ":获取结果:" + throwable.getLocalizedMessage());
            return null;
        });
        System.out.println(Thread.currentThread().getName() + ":main执行完毕");

执行结果:

main:main开始完毕
pool-1-thread-1:子任务开始完毕
main:main执行完毕
pool-1-thread-1:获取结果:java.lang.ArithmeticException: / by zero

可以看出这异常结果的获取与任务执行的线程池一致,只是这个方法只需要获取执行的异常信息,无需获取执行的结果,通过上面的三个方法也可以办到。 好了,到这里,我们已经能够获取到任务的执行结果了,但是我们上面获取的执行结果都是针对单一的任务获取结果,如果存在多个任务,需要同时执行完成后才可以进行后续操作,就必须我在异步编程同步和异步概念中举的根据获取文章的阅读量,访问量,转发量后来生成报表数据,好像上面只是单单的获取单一任务的执行结果来完成,需要自己去判断是否三个任务都已经执行完毕,待三个任务执行完毕后,在去执行后面的报表任务,但是我们在等待执行的结果时,又会成为阻塞任务,会阻塞任务的后续流程执行,浪费资源,加大了任务的执行时间影响了执行效率,针对这种案例场景,CompletableFuture给我们提供了另外一套方法,用来实现多任务的合并执行相关操作。 哈哈,又到了博主说请听下回分解的时候,老规矩,请看下一篇文章哦!!!