深入理解Kotlin协程
上QQ阅读APP看书,第一时间看更新

1.3.2 CompletableFuture

从某种意义上来讲,通过阻塞当前调用来等待异步结果,让异步的逻辑变得不像“异步”了,是因为我们还得同步地等待结果。因此JDK 1.8又新增了一个CompletableFuture类,它实现了Future接口,通过它我们可以拿到异步任务的结果,此外,它还有很多更好用的方法。我们先将之前的代码改造成CompletableFuture的形式,见代码清单1-15。

代码清单1-15 返回CompletableFuture的异步函数


fun bitmapCompletableFuture(): CompletableFuture<Bitmap>
  = CompletableFuture.supplyAsync { 
      ... // 省略获取图片的逻辑
    }

使用CompletableFuture来获得结果就显得更巧妙了,如代码清单1-16所示。

代码清单1-16 整合多个CompletableFuture的结果


urls.map {
  bitmapCompletableFuture(it)
}.let { futureList ->
  CompletableFuture.allOf(*futureList.toTypedArray())
    .thenApply {
      futureList.map { it.get() }
    }
}.thenAccept { bitmaps ->
  ... // 省略处理图片
}

同样,我们通过urls得到了bitmaps,并且thenAccept只会在结果就绪时回调,因此这段逻辑也不会阻塞整体代码的执行流程。

当然,CompletableFuture的API还不是十分好用,本例当中我们为了整合多个CompletableFuture<Bitmap>,要先通过allOf构造出一个CompletableFuture<Void>,后者会在所有的Bitmap都就绪时回调它自己的thenApply,我们再通过get函数一一拿到对应的Bitmap。这个过程完全可以提供一个API供开发者使用,我们可以使用Kotlin的扩展函数来试着提供这个实现,如代码清单1-17所示。

代码清单1-17 整合CompletableFuture结果的API


fun <T> List<CompletableFuture<T>>.allOf(): CompletableFuture<List<T>> {
  return CompletableFuture.allOf(*this.toTypedArray())
    .thenApply {
      this.map { it.get() }
    }
}

这样我们前面的代码就可以进一步简化了,如代码清单1-18所示。

代码清单1-18 简化的CompletableFuture调用


urls.map {
  bitmapCompletableFuture(it)
}.allOf().thenAccept { bitmaps ->
  ... // 省略处理图片
}

与直接使用Future不同,get函数的调用仍然在CompletableFuture提供的异步调用环境当中,不会阻塞主调用流程。

CompletableFuture算是JDK提供的很好用的异步API,它解决了异步结果阻塞主调用流程的问题,但却让结果的获取脱离了主调用流程。那么,有没有既不阻塞又不脱离主调用流程的办法呢?