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 协程~

相关推荐
JohnnyDeng942 小时前
Android 包体积优化:R8/ProGuard 深度配置
android
qq_452396232 小时前
第六篇:《JMeter逻辑控制器:循环、条件和交替执行》
android·java·jmeter
高林雨露3 小时前
kotlin by 和 = 的区别在于【属性委托】和直【接赋值】的差异
kotlin
cwzqf4 小时前
Jectpack Compose项目组件代码分享(1):分页加载组件
android
@北海怪兽4 小时前
SQL常见函数整理 _ STRING_AGG()
android·数据库·sql
鹏晨互联5 小时前
【Compose vs XML:边框内外间距的实现对比】
android·xml
Android系统攻城狮5 小时前
Android tinyalsa深度解析之pcm_plugin_write调用流程与实战(一百七十九)
android·pcm·tinyalsa·android16·音频进阶·android音频进阶
ID_180079054736 小时前
除了JSON,淘宝店铺商品API接口还支持哪些数据格式?
android·数据库
KillerNoBlood6 小时前
2026移动端跨平台开发面经总结
android·算法·flutter·ios·移动开发·鸿蒙·kmp
消失的旧时光-19436 小时前
Android / IoT 面试复盘总结:从 MQTT、TLS 到 JWT 权限体系(标准答案 + 工程理解 + 延伸知识链)
android·物联网·面试