Kotlin 协程源代码泛读:Continuation 思想实验

考虑这样一个故事(场景),我们要从网络上下载图片并展示

kotlin 复制代码
fun display(url: String) {
    val data = download(url)
    val image = decode(data)
    show(image)
}

download 和 decode 是耗时操作,不能在 Ui 线程中执行

所以修改 download 和 decode,让它们异步工作,完成后通过 callback 回调

这里特意把 callback 定义成和 Kotlin 中同样的名字 Continuation

kotlin 复制代码
fun display(url: String) {
    download(url) {
        decode(image) {
            show(image)
        }
    }
}

fun download(url: String, continuation: Continuation) {}
fun decode(data: ByteArray, continuation: Continuation) {}

我们优化一下上面这种回调写法,实际上也是 Kotlin 协程框架采用的方案:

kotlin 复制代码
fun display(url: String) {
    val continuation = object: Continuation {
        var label = 0
        var result: Any? = null
        fun resumeWith(result: Any) {
        }
    }
    when (continuation.label) {
        0 -> {
            continuation.label = 1
            download(url, continuation)
            return
        }

        1 -> {
            continuation.label = 2
            decode(continuation.result as ByteArray, continuation)
            return
        }
    }
    display(continuation.result as Image)
}
  • continuation 的 label 字段标识不同的阶段
  • when 执行display 函数不同的阶段/部分
  • download 和 decode 执行完毕后调用 continuaton 的 resumeWith 方法通知调用者

这里有个问题,resumeWith 咋实现? 我们需要修改方案吗?比如把 when代码快挪到 resumeWith 里头? Kotlin 协程框架采用的方法是,给 display 增加一个 continuation 参数,然后递归调用!

kotlin 复制代码
fun display(url: String, continuation: Continuation? = null) {
    // 这里有个调用约定,开始时 continuation 为 null
    val continuation = continuation ?: object: Continuation {
        var label = 0
        var result: Any? = null
        fun resumeWith(result: Any) {
            continuation.result = result
            // 递归调用
            display(url, continuation)
        }
    }

    ...
}

当然这个 continuation 参数另有作用!

扩展一下这个场景,display 后,我们想在 display 之后干点什么!

你肯定会想那简单啊,我们已经有 continuation了!

kotlin 复制代码
fun page() {
    val continuation = object: Continuation {
        fun rewumeWith(result: Any?) {
            // 干点什么~
        }
    }
    display(url, continuation)
}

问题又来了,之前我们约定调用 display 作为入口调用的时候 continuation 需要传 null...

这里也不卖关子了,Kotlint 的解决方案是类型判断!

kotlin 复制代码
fun display(url: String, completion: Continuation? = null) {
    var $continuation: Continuation? = null
    if (continuation is ContinuationImpl) {
        // 自己人!(递归调用) 
    } else {
        // 异乡人!,注意这里保存了 completion
        $continuation = ContinuationImpl(completion)
    }
   // 定义一个内部类
   inner class ContinuationImpl(
       completion: Continuation
   ): Continuation {
       fun resumeWith(result: Any?) {
           ...
       }
   }
   ...
   show(...)
   continuation?.resumeWith(...)
}

好了,故事讲完了,回到现实中

这个故事的 suspend 函数版本如下

kotlin 复制代码
suspend fun display(url: String) {
    val data = download()
    val image = decode(data)
    show(image)
}

suspend fun download(url: String) {}
suspend fun decode(data: ByteArray) {}

感谢 Kotlin 协程~

相关推荐
数智工坊7 小时前
机器人运动控制:采样、优化与学习三大流派深度对比与实战
android·学习·机器人
故渊at8 小时前
第二板块:Android 四大组件标准化学理 | 第八篇:Service 后台执行实体与优先级
android·gitee·service·前台服务·后台服务
会Tk矩阵群控的小木8 小时前
安卓群控系统对于游戏工作室实战教程
android·运维·游戏·adb·开源软件·个人开发
qeen879 小时前
【C++】类与对象之类的默认成员函数(二)
android·c语言·开发语言·c++·笔记·学习
故渊at10 小时前
第二板块:Android 四大组件标准化学理 | 第九篇:BroadcastReceiver 事件分发与有序广播
android·gitee·broadcast·广播·动态注册·静态注册
JohnnyDeng9410 小时前
【Android】Room 数据库高级用法与性能调优:从查询瓶颈到毫秒级响应
android·性能优化·kotlin·room
zeqinjie10 小时前
Flutter 折叠屏 iPad / 宽屏适配实践
android·前端·flutter
ab_dg_dp10 小时前
Android 17+ 提取 AIDL 生成 Java 文件的实用脚本
android·java·python
Arrom11 小时前
DLNA 渲染端排障实战:从 20s 卡顿到 stale subscriber 的两周追凶之旅
android·java