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

相关推荐
CaspianSea15 小时前
编译Android 16 TV模拟器(一)
android
廋到被风吹走19 小时前
【数据库】【MySQL】InnoDB外键解析:约束机制、性能影响与最佳实践
android·数据库·mysql
峥嵘life20 小时前
Android16 EDLA 认证测试CTS问题分析解决
android·java·服务器
惟恋惜20 小时前
Jetpack Compose 的状态使用之“界面状态”
android·android jetpack
_李小白1 天前
【Android FrameWork】第二十六天:BroadcastReceiver
android
@#---1 天前
如何准确判断json文件并且拿到我想要的信息
android·python·json
KotlinKUG贵州1 天前
Kotlin/Ktor 实践:利用 MCP 从零打造 AI Agent 服务端指南
kotlin·agent·mcp
喜熊的Btm1 天前
探索 Kotlin 的不可变集合库
kotlin·android jetpack
程序员陆业聪1 天前
Android插件化原理与方案详解
android
惟恋惜1 天前
Jetpack Compose 界面元素状态(UI Element State)详解
android·ui·android jetpack