首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

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

  • 25-04-23 14:41
  • 4607
  • 11853
juejin.cn

接着上回 Continuation 思想实验,我们来思考另一个问题,如何实现 download

kotlin
代码解读
复制代码
fun download(url: String, completion: Continuation) { // 下载中... completion.resumeWith(...) }

聪明的你一定能想到各种各样的方法,最简单粗暴的做法,可以这样

kotlin
代码解读
复制代码
fun download(url: String, completion: Continuation) { val thread = new Thread {      // 下载中...      completion.resumeWith(...) } }

这种史前时代的写法,Code Review 的时候会被提 N 个问题,简单改进一下

kotlin
代码解读
复制代码
val dispatchers = Executors.newCachedThreadPool() fun download(url: String, completion: Continuation) { dispatchers.execute {      // 下载中...      completion.resumeWith(...) } }

稍微好点,但是还是有一堆问题,我们点到为止
其它类似的耗时函数,比如 decode 也需要进行类似的改造。代码书写信条:DO NOT REPEAT YOURSELF!告诉你这些函数有相同的样板代码(Executor.execute),而且它和具体的业务(下载,解码)无关,所以更应出现在框架层。那么有没有其它解法呢?

Continuation 思想实验提到带 Continue 参数的方法就像状态机,Continuation.resumeWith 可以在 方法内不同的 label(挂起点)「恢复」执行,我们来看看 download 的调用者 display:

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 {      var label: Int = 0      var result: Any? = null      // resumeWith 让 display 从某个 label 恢复执行      fun resumeWith(result: Any?) {          completion.ressult = result          display(url, completion) } } when (continuation.label) {      0 -> {      // 下次 resume 该执行 1 了          continuation.label = 1 download(url, continuation)  return }      1 -> {          // 下次 reusme 执行 2,when 代码块之后剩下的代码块          continuation.label = 2 decode(continuation.result as ByteArray, continuation)  return } } show(...) continuation?.resumeWith(...) }

这个 resumeWith 就是突破口! resume 有恢复、唤醒的意思,想象你坐卧铺车从一个乡下到城市,上车后你睡下(suspend)了,然后睡一觉在城里的火车站醒来(resume)。我们如何实现这种在一个线程挂起,然后其它线程恢复的效果呢?答案是:

bash
代码解读
复制代码
调用者(caller)进行线程切换

使用代理模式,偷梁换柱,拦截(intercepted)resumeWith 调用

kotlin
代码解读
复制代码
class DispatchedContinuation(     val executor: Executor,      val delegate: Continuation ): Continuation, Runnable { fun resumeWith(result: Any) {      executor.submit {          delegate.resumeWith(result) } } }

为了方便使用,定义一个扩展方法

kotlin
代码解读
复制代码
fun Continuation.intercepted(executor: Executor): Continuation {     return DispatchedContinuation(this) }

修改 display 方法:

kotlin
代码解读
复制代码
fun display(url: String, executor: Executor, completion: Continuation? = null) {    var $continuation: Continuation? = null if (continuation is ContinuationImpl) {         // 自己人!(递归调用)  } else {      // 异乡人!,注意这里保存了 completion      // 对 ContinuationImpl 进行 wrap,这样就可以在 Executor 中 resume 啦      $continuation = ContinuationImpl(completion).intercepted(executor) } ... }

这个 executor 参数,所有的异步方法都需要,进一步抽象&封装: 

kotlin
代码解读
复制代码
interface Scope {     val context: Context } interface Context {     val executor: Executor } fun Scope.display(url: String, completion: Continuation? = null) {     ... }

这样可以让调用者指定 executor,是不是有那么一丢丢协程的味道了! 在我们上述思想的过程中,已经触及到协程中一个重要的概念 Dispatcher(调度器),以及它是如何和 Continuation 协作的

最后再考虑一个问题,目前的封装可以让 display 在指定的 Executor 中 resume,但是 diaplay 第一个 resume 之前(when 语句块中的 label 0)呢?我们可以指定 display 在哪个线程启动吗? 我们把 display 也变成状态机:

kotlin
代码解读
复制代码
class DisplayContinuation( val scope: Scope, ): Continuation { fun resumeWith(result: Any?) {      invokeSuspend(result as url) } fun invokeSuspend() { // 这里有点问题      scope.display(url, this) } }

ok,现在对 display 的调用,变成这样

scss
代码解读
复制代码
DisplayContinuation(scope).resumeWith(...)

如果想在工作线程中调用(调度)display,就可以这样写

scss
代码解读
复制代码
DisplayContinuation(scope).intercepted().resumeWith(...)

如果又来了个其它的任务,比如下载和更新应用,你也可以写一个类似的 UpdateContinuation,它们两有重复(相似)的代码,所以可以提取一个基类

kotlin
代码解读
复制代码
class BaseContinuationImpl: Continuation { fun resumeWith(result: Any?) {     invokeSuspend(result) }     abstract fun invokeSuspend(param: Any?) } DisplayContinuation: BaseContinuationImpl {...}

如果这么抽象和封装下去,你会搞出个类似 launch 的协程启动器...

这些近乎玩具的代码,只是为了展示Kotlin协程基本概念,具体实现的时候,需要考虑很多问题!

注:本文转载自juejin.cn的老码识土的文章"https://juejin.cn/post/7496052803417620490"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

140
Android
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top