聊聊Kotlin协程中Continuation的魔法

简介: 聊聊Kotlin协程中Continuation的魔法

1. 前言


这是新年以来的第一篇更文,在此给大家拜个晚年,祝大家在新的一年所想的都能如愿,同时感谢大家一直以来的支持和帮助。这篇文章其实在春节前就已经构思完了,本想着在留京过年期间写完,由于计划变更,回老家过年去了,春节期间大部分时间在走亲戚,文章也就搁置下来了。


闲话少叙,本文我将尝试给大家讲明白,Kotlin协程如何实现用同步的方式实现异步调用。相信不少同学都能说出以下几种概念中的一个或者多个。


  • Kotlin suspend关键字
  • Kotlin内部的Continuation机制
  • Continuation Passing Style (CPS)机制
  • 有限状态机机制


如果你看过Kotlin协程架构师的Deep dive into Coroutines on JVM演讲视频,相信你对上面的概念不会陌生。

640.jpg


视频链接👉https://www.youtube.com/watch?v=YrrUCSi72E8&t=1329s

但是如果你没有深入源码探究其实现原理,相信你对上面的概念也是一知半解。本文将带领大家更加详细地了解这一知识。


2. 一个循序渐进的例子


假设有这样一个简单的场景,在App上发起一个请求,10s后拿到响应,更新到用户界面上。我们可能会遇到以下几种写法。


2.1 直接在主线程调用


640.png


这种情况,大家都很清楚,对于客户端开发,在主线程执行耗时操作这是万万不可的。为了不阻塞主线程,我们需要通过开启新线程,使用回调的方式,将结果回传过来。


2.2 使用callback

640.png


这种方式,虽然解决了在主线程做耗时操作的问题,但是引入了新问题,在子线程更新UI,会导致应用崩溃。为了解决这个问题,我们需要通过主线程Handler,把callback post到主线程运行。


2.3 使用callback和handler组合

640.png


目前为止,我们通过线程,回调,Handler组合方式,完美地实现了执行一个异步请求,并将结果更新到UI上。


在这个方案中有三个要素:


1. 线程或者线程池技术


2. 回调机制,将结果反馈给调用者


3. 回调操作在哪个线程中执行



那么此处应该敲黑板划重点了

既然本文是讲协程,那么协程中哪些类分别对应这三个要素呢?

  1. Dispatchers.Main、Dispatchers.IO等对应线程
  2. Continuation对应线程中的回调
  3. DispatchedContinuation同时指定了Continuation回调,又指定了回调在哪个线程中执行。

在此先不展开讲解Continuation和DispatchedContinuation的具体细节,此处做个铺垫,后文会详细讲解。


2.4 使用协程实现

640.png

该代码是协程实现的正确代码,没有主线程执行耗时操作问题,没有子线程更新UI问题。当然也没有显式的callback,和线程切换的代码。只对suspendHeavyWork方法增加了suspend关键字,就能将异步的调用 用同步的代码实现。那么魔法在哪呢?


3. 魔法揭秘


首先贴一下完整版代码

640.png

使用Android Studio的Show Kotlin Bytecode功能查看反编译后的文件

640.png


感慨x1看了反编译后的代码,可能需要感慨一下,哪有什么岁月静好,只不过有人在替你负重前行。用协程写代码爽,是因为编译器在后面做了不少工作,理解这背后的工作,对我们使用协程大有裨益。


困惑x1那么编译器生成的这些代码,看起来既熟悉又陌生,说熟悉是因为就语法而言每行代码都认识(switch case都很熟悉),说陌生是因为总体而言,不太明白他们干了什么,甚至有的函数(比如invokeSuspend)连在哪调用的都不知道。


困惑x2更让人困惑的是,状态机代码中明明出现了return语句,请问后续的代码是如何执行的?要搞清楚状态机的原理必须搞清楚这个问题。


640.png

4. 研究launch反编译



640.png


上图标出了三个重要的地方。


  1. lambda表达式被转换成了Function2实例,那么Function2是什么?为什么又给Function2传了一个类型为Continuation的null对象?
  2. invokeSuspend方法是干嘛的?注意参数是非空的。
  3. 调用heavyWork(this)传入了this对象。从前文我们可以看到,heavyWork方法反编译之后变成了heavyWork(Continuation var)。那么说明Function2是Continuation类型。


有了以上几个问题,那么离揭晓答案也就不远了。


4.1 invokeSuspend


首先回答最简单的那个问题,invokeSuspend方法是干嘛的。它定义在BaseContinuationImpl类中,是一个抽象方法。

640.png

640.png

从BaseContinuationImpl(public val completion:Continuation<Any?>?)我们可以知道协程中的回调是用链表串起来的。

640.png


假设有suspend函数调用如下,那么Continuation关系图如下

640.png

640.png

640.png


  1. 首先一个while循环,它保证了状态机能够轮询。
  2. val completion = completion!! 如果当前Continuation 左边没有回调了,快速返回
  3. val outcome = invokeSuspend(param) 调用当前Continuation的 invokeSuspend方法
  4. 如果outcome === COROUTINE_SUSPENDED直接返回。这就是为什么delay方法不会阻塞当前线程的原因,遇到suspend方法 label会+1,当前Continuation会传递给delay。
  5. if (completion is BaseContinuationImpl)继续递归调用invokeSuspend
  6. 否则调用completion.resumeWith(outcome)并且返回


这段代码就是保证状态机能够运行的核心。


4.2 Function2是什么?


Function2是SuspendLambda,定义在ContinuationImpl.kt中。它是BaseContinuationImpl的子类。

640.png


5. DispatchedContinuation


internal class DispatchedContinuation<in T>(
    @JvmField val dispatcher: CoroutineDispatcher,
    @JvmField val continuation: Continuation<T>
)

DispatchedContinuation 有dispatcher和continuation两个成员变量。表示在dispatcher所对应的线程,执行continuation回调。在发生线程切换时,一定会生成DispatchedContinuation对象,否则,切完线程后,就无法再切回来了。


例如 delay和withContext,切换线程后都会创建DispatchedContinuation,以记录回调要在哪个线程调用。

640.png


640.jpg

640.jpg

相关文章
|
5月前
|
Java 数据库 Android开发
【专栏】Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
133 5
|
5月前
|
传感器 Android开发 开发者
构建高效Android应用:Kotlin的协程与Flow
【4月更文挑战第26天】随着移动应用开发的不断进步,开发者寻求更简洁高效的编码方式以应对复杂多变的业务需求。在众多技术方案中,Kotlin语言凭借其简洁性和强大的功能库逐渐成为Android开发的主流选择。特别是Kotlin的协程和Flow这两个特性,它们为处理异步任务和数据流提供了强大而灵活的工具。本文将深入探讨如何通过Kotlin协程和Flow来优化Android应用性能,实现更加流畅的用户体验,并展示在实际开发中的应用实例。
|
5月前
|
移动开发 Java Android开发
构建高效Android应用:Kotlin与协程的完美融合
【2月更文挑战第25天】 在移动开发领域,性能优化和应用响应性的提升是永恒的追求。随着Android Jetpack组件库的不断丰富,Kotlin语言已经成为Android开发的首选。而Kotlin协程作为一种新的并发处理方案,它以轻量级线程的形式,为开发者提供了简洁高效的异步编程手段。本文将深入探讨Kotlin协程在Android应用中的实践运用,以及如何通过这种技术改善用户界面的流畅度和后台任务的处理能力,进而构建出更高效、更稳定的Android应用。
|
5月前
|
安全 Android开发 开发者
构建高效Android应用:Kotlin与协程的完美结合
【2月更文挑战第30天】在移动开发领域,性能优化和流畅的用户体验是关键。本文深入探讨了如何通过结合Kotlin语言和协程技术来提升Android应用的性能和响应能力。我们将分析Kotlin的优势,介绍协程的基本概念,并通过实际案例展示如何在应用中实现协程以简化异步编程,从而提供更加高效的解决方案。
|
7天前
|
JSON 调度 数据库
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
10 1
|
2月前
|
调度 开发者 UED
Kotlin 中的协程是什么?
【8月更文挑战第31天】
129 0
|
4月前
|
存储 Java 调度
Android面试题之Kotlin 协程的挂起、执行和恢复过程
了解Kotlin协程的挂起、执行和恢复机制。挂起时,状态和上下文(局部变量、调用栈、调度器等)被保存;挂起点通过`Continuation`对象处理,释放线程控制权。当恢复条件满足,调度器重新分配线程,调用`resumeWith`恢复执行。关注公众号“AntDream”获取更多并发知识。
107 2
|
5月前
|
移动开发 Android开发 开发者
构建高效Android应用:Kotlin与协程的完美融合
【5月更文挑战第25天】 在移动开发的世界中,性能和响应性是衡量应用质量的关键指标。随着Kotlin的流行和协程的引入,Android开发者现在有了更强大的工具来提升应用的性能和用户体验。本文深入探讨了Kotlin语言如何与协程相结合,为Android应用开发带来异步处理能力的同时,保持代码的简洁性和可读性。我们将通过实际案例分析,展示如何在Android项目中实现协程,以及它们如何帮助开发者更有效地管理后台任务和用户界面的流畅交互。
|
5月前
|
移动开发 监控 Android开发
构建高效安卓应用:Kotlin 协程的实践与优化
【5月更文挑战第16天】 在移动开发领域,性能优化一直是开发者们追求的重要目标。特别是对于安卓平台来说,由于设备多样性和系统资源的限制,如何提升应用的响应性和流畅度成为了一个关键议题。近年来,Kotlin 语言因其简洁、安全和高效的特点,在安卓开发中得到了广泛的应用。其中,Kotlin 协程作为一种轻量级的并发解决方案,为异步编程提供了强大支持,成为提升安卓应用性能的有效手段。本文将深入探讨 Kotlin 协程在安卓开发中的应用实践,以及通过合理设计和使用协程来优化应用性能的策略。
57 8
|
5月前
|
移动开发 数据库 Android开发
构建高效Android应用:探究Kotlin的协程优势
【5月更文挑战第22天】随着移动开发技术的不断进步,Android平台的性能优化已经成为开发者关注的焦点。在众多提升应用性能的手段中,Kotlin语言提供的协程概念因其轻量级线程管理和异步编程能力而受到广泛关注。本文将深入探讨Kotlin协程在Android开发中的应用,以及它如何帮助开发者构建出更高效、响应更快的应用,同时保持代码的简洁性和可读性。