1.2 异步程序设计的关键问题
1.2.1 结果传递
不同于同步调用,异步调用是立即返回的,因此被调用方的逻辑通常存在2种情形:
结果尚未就绪:进入任务执行的状态,等结果就绪后通过回调传递给调用方
结果已经就绪,可以立即提供结果
用图片获取作为例子,上述2种情况代码示意如下所示(这是我个人觉得理解Kotlin协程实现最巧妙的例子了,其实后面的本质就是这个):
1 | // 方法实现 |
Kotlin 协程的挂起函数 (suspend function) 本质上就是采取了这个异步返回值的设计思路
1.2.2 异常处理
我们希望异步的任务在 suspend 的时候执行,执行完成后 resume 回来,之后再同步判断是否发生了异常。这样逻辑就会简单很多。异步逻辑同步化也是 Kotlin 协程要解决的问题。
1.2.4 复杂分支
为同步操作添加分支甚至循环操作是很容易的,比如获取图片是同步的情况下,获取多个图片:
1 | val bitmap = urls.map { syncBitmap(it) } |
在上述情况下我们甚至还能方便地通过一个 try-catch 捕捉所有异常。而当获取图片的操作是异步的情况时,就变得复杂,还需要用到一些同步工具来辅助:
1 | val countDownLatch = CountDownLatch(urls.size) |
这里还需要借助 CountDownLatch 门闩 来实现,因此在 ① 处会阻塞当前线程,直到所有回调的 ② 或 ③ 位置执行之后,才会执行获取所有图片的操作:val bitmaps = map.values ,总之,这个过程还是挺复杂的。
EMPTY_BITMAP 是一个空的 Bitmap 对象,为什么要这么做呢?因为 ConcurrentHashMap 不允许 value 为空!
1.3 常见异步程序设计思路
1.3.1 Future
但是 Future.get() 方法会造成当前调用阻塞。
1.3.2 CompletableFuture
它的 get() 方法无需阻塞,异步调用不阻塞主流程调用但是结果脱离了主调用流程(需要回调获取)
1.3.3 JavaScript 的 Promise——async/await
比较完好地实现了需求,他们 async/await 的语义稍微不同。
1.3.5 Kotlin协程
Kotlin 协程是为异步程序设计而生的。
有人称协程只是“一个线程框架”,认为协程就是用来切换线程的,显然有点“一叶障目不见泰山”了
Kotlin 协程用一个 suspend 关键字,包含了异步调用 和 回调 两层含义。我们知道,所有异步回调对于当前调用流程只是一个挂起点。在这个挂起点可以做的事情非常多:既可以做异步回调,还可以添加调度器来处理线程切换,还可以作为协程取消响应的位置。
看一个 Kotlin 处理异步调用的例子:
1 | suspend fun bitmapSuspendable(url: String): Bitmap = |
suspend 修饰函数,意味着支持同步化的异步调用。上述代码中,
continuation.resume(download(url)) 将正常的结果返回
continuation.resumeWithException(e) 则是将异常返回,在调用 bitmapSuspendable() 方法时,如果产生异常会把这个异常抛出
效果如下:
1 | suspend fun main() { |
从 1.3.0 开始,开始支持 suspend fun main 作为函数入口了