Lambda从入门到精通之三十九 CompletableFuture异步编程的错误处理

CompletableFuture是用来进行异步编程的,核心代码都放到了另一个线程计算,那么计算的线程如果发生了异常,改如何处理呢?
我们先看demo,demo的功能和CompletableFuture第一节是一样的,查询多个商品的价格,但是其中某个商品的价格获取非常耗时,所以采用了CompletableFuture异步计算,看代码:

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * 使用CompletableFuture异步编程
 * @author www.itzhimei.com
 */
public class FutureTest_2 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //进行异步价格计算
        Future<Integer> priceA = getPriceAsync();

        //执行其他业务逻辑
        printOtherPrice();

        //获取价格计算结果
        Integer price = priceA.get();
        System.out.println("异步获取价格" + price);

    }

    public static void printOtherPrice() {
        for(int i=0; i<10; i++) {
            System.out.println("获取其他商品价格:"  + new Random().nextInt());
        }
    }

    public static Future<Integer> getPriceAsync() {
        CompletableFuture<Integer> future = new CompletableFuture<>();
        new Thread(()->{
            try {
                //模拟计算复杂逻辑
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            future.complete(new Random().nextInt());
        }).start();
        return future;
    }
}
/* 输出
获取其他商品价格:450142711
获取其他商品价格:-1207991723
获取其他商品价格:-264920736
获取其他商品价格:1122276343
获取其他商品价格:-652082111
获取其他商品价格:1312307709
获取其他商品价格:-417893410
获取其他商品价格:1957242712
获取其他商品价格:1257272721
获取其他商品价格:100803481
异步获取价格-1514711276
 */

1、一种方法是在获取异步结果的时候做调整
CompletableFuture的get方法和Future的get方法一样,都是获取异步结果,如果没有结果(发生异常)或者结果计算特别慢,则程序一直阻塞在此,那么此时可以使用另一个get方法。
get(long timeout, TimeUnit unit),这个方法可以设置等待时间,超过等待时间则继续执行后续代码。

2、第二种是进行异常返回
如果调用的异步线程中发生异常,此时发起调用的线程是不知道具体错误的,CompletableFuture提供了可以返回异步线程内的错误的方法futurePrice.completeExceptionally(ex),我们demo的代码可以做一些调整,如下:

public static Future<Integer> getPriceAsync() {
	CompletableFuture<Integer> future = new CompletableFuture<>();
	new Thread(()->{
		try {
			//模拟计算复杂逻辑
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		try {
			int a = 10;
			int b = 0;
			future.complete(a/b);//模拟一个异常
			//future.complete(new Random().nextInt());
		} catch (Exception ex) {
			future.completeExceptionally(ex);
		}
	}).start();
	return future;
}

/* 输出
获取其他商品价格:5158158
获取其他商品价格:-787358034
获取其他商品价格:1026159408
获取其他商品价格:1082639987
获取其他商品价格:227190370
获取其他商品价格:-1008236480
获取其他商品价格:-267365833
获取其他商品价格:-346777568
获取其他商品价格:1282838883
获取其他商品价格:-2081155736
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2237)
	at com.itzhimei.FutureTest_3.main(FutureTest_3.java:23)
Caused by: java.lang.ArithmeticException: / by zero
	at com.itzhimei.FutureTest_3.lambda$getPriceAsync$1(FutureTest_3.java:46)
	at com.itzhimei.FutureTest_3$$Lambda$1/1323165413.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)
 */

程序会先返回一个java.util.concurrent.ExecutionException异常,后面跟上代码实际的异常,这样,调用方也可以知道错误原因,并进行相应的异常处理。

3、使用whenComplete方法

CompletableFuture的whenComplete方法可以对结果再次进行处理,如果正常返回结果则返回,如果异常则可以定义异常处理逻辑进行处理。
CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)方法的参数是一个二元的消费函数,处理函数有两个参数void accept(T t, U u),第一个是业务逻辑处理结果,第二个是异常结果,二者其中一个总是为null。
例如:
//执行异常
String join2 = CompletableFuture.supplyAsync(() -> FutureTest_9.getTaskResultWithException(500, "Test Task 2"))
		.whenComplete((x, e) -> {
			System.out.println("计算结果:" + x);
			System.out.println("异常信息:" + e);
		}).join();
System.out.println(join2);

4、在whenComplete方法后面继续添加一个异常处理方法,这也是CompletableFuture的大多数方法都可行的异常处理方案。

//执行异常,捕获异常并处理
String join3 = CompletableFuture.supplyAsync(() -> FutureTest_10.getTaskResultWithException(500, "Test Task 2"))
		.whenComplete((x, e) -> {
			System.out.println("计算结果:" + x);
			System.out.println("异常信息:" + e);
		}).exceptionally(e-> "捕获异常,返回为空").join();
System.out.println(join3);