随手记

问题背景:

使用FutureTask来做异步任务,但是当需要获取返回值,futureTask.get()方法却是非异步执行的,不符合需求,如果还想在任务完成后记录日志什么的,更不用考虑了。

问题研究:

使用CompletableFuture代替FutureTask。

CompletableFuture简单使用方式如下:

public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
                System.out.println("future task is called");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("future task is done");
                return "future task is done";
        });
        future.thenAccept((result) -> {
            System.out.println("执行完成!");
        });
        future.exceptionally(t -> {
            System.out.println("执行失败!"+t.getMessage());
            return null;
        });
//        Thread.sleep(3000);
    }

 结果输出:

Android 异步带返回值 task异步返回值_User

是的,问题来了,当使用main方法测试时,发现主线程执行完毕后,程序就会结束,导致任务执行完毕回调的方法没有触发。当设置sleep3秒时,才可以打印出来。

public static void main(String[] args) throws InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
                System.out.println("future task is called");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("future task is done");
                return "future task is done";
        });
        future.thenAccept((result) -> {
            System.out.println("执行完成!");
        });
        future.exceptionally(t -> {
            System.out.println("执行失败!"+t.getMessage());
            return null;
        });
        Thread.sleep(3000);
    }

结果:

 

Android 异步带返回值 task异步返回值_Android 异步带返回值_02

但是,实际应用中,谁也不知道任务要执行多久,这种方法好像有点问题......

问题解决:

但是后来又仔细一想,又好像有点不对,实际的应用场景不需要考虑主线程结束的问题。

因为这个这个任务是放到springboot网站中跑的,而springboot的主线程好像不会停!不然网站就挂了啊!

从main转到网站中测试后,果然没问题。

public Object work(FutureUtils job, DownloadTask downloadTask, DownloadTaskService downloadTaskService) {
        CompletableFuture<Object> future = CompletableFuture.supplyAsync(job::run);
        future.thenAccept((result) -> {
            Date ed = new Date();
            downloadTask.setEndTime(ed);
            downloadTask.setTaskStatus("S");
            downloadTask.setTaskProgress(100.0);
            downloadTask.setUsingTime(ed.getTime() - downloadTask.getStartTime().getTime());
            downloadTaskService.downloadTaskUpdate(downloadTask);
            System.out.println("执行完成!");
        });
        future.exceptionally(e -> {
            String msg = e.toString();
            log.error("任务执行失败!{}", msg);
            Date ed = new Date();
            downloadTask.setEndTime(ed);
            downloadTask.setTaskStatus("E");
            downloadTask.setUsingTime(ed.getTime() - downloadTask.getStartTime().getTime());
            downloadTask.setMsg(msg.substring(0, Math.min(msg.length(), 2000)));
            downloadTaskService.downloadTaskUpdate(downloadTask);
            return null;
        });
        return null;
    }

坑一:

如果定义的job有异常抛出,千万不要使用catch去捕获,否则外层的 future.exceptionally无法捕获异常,无法统一记录日志。

但是如果把job中的异常往外抛,则job方法的定义势必会声明有异常抛出,那问题来了,在CompletableFuture.supplyAsync中自定义的方法

new Supplier<Object>(){},它只有一个方法get,而这个get是override的,父类并没有抛出异常,也就是说,这个异常抛不出了......而在里面catch了,就无法统一异常处理。

还好有个nb的注解@SneakyThrows,这样就不需要抛异常,也不需要catch了。有异常时,future.exceptionally也可以正常捕获!

CompletableFuture<Object> future = CompletableFuture.supplyAsync(new Supplier<Object>() {
            @SneakyThrows
            @Override
            public Object get()  {
                return job.run();
            }
        });

多任务拓展:

多任务异步执行,一批任务都执行完毕后,执行某个方法:

@Data
public class User {
    private String name;
    private String age;
    User(){}
    User(String name,String age){
         = name;
        this.age = age;
    }
}
public class CompleteFutureTest {

    private static List<User> shops = Arrays.asList(new User("shop1","1"),
            new User("shop2","1"),
            new User("shop3","1"),
            new User("shop4","1"),
            new User("shop5","1"),
            new User("shop6","1"),
            new User("shop7","1"),
            new User("shop8","1")
    );

    public static void main(String[] args) throws InterruptedException {
       //定义任务列表
        List<CompletableFuture<String>> priceFuture = shops.stream().map(shop -> CompletableFuture
                .supplyAsync(() -> String.format("%s price is %s ", shop.getName(), shop.getAge())))
                .collect(Collectors.toList());

        //定义每个任务完成后做的事情
        priceFuture.forEach(futureResult ->{
            futureResult.thenAccept(CompleteFutureTest::allDone);
        });

      // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
        Thread.sleep(30000);
    }

    public static final  int count = shops.size();

    public static AtomicInteger atomicInteger = new AtomicInteger(0);

    static void allDone(String s) {
        int result = atomicInteger.addAndGet(1);
        System.out.println(result);
        if(count == result){
            System.out.println("all done");
        }
    }
}