探秘Kotlin协程机制
什么是协程
- 场景1:异步回调嵌套
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fj2umvsw-1641110474367)(image-20211209143744495.png)]
//客户端顺序进行三次网络异步请求,并用最终结果更新UI
request1(paramter){ value1->request2(value1){ value2->request3(value2){ value3->updateUI(value3)}}
}
这种多个回调嵌套耦合非常不利于代码的维护和阅读
协程的写法
GlobalScope.launch(Dispatcher.Main){val value1 = request1()val value2 = request(value1)val value3 = request(value2)updateUI(value3) }suspend request1() suspend request2(..) suspend request3(..)
场景2:并发流程控制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d0W62Zk0-1641110474367)(image-20211209145312666.png)]
fun request1(parameter){ value1 ->request2(value1) { value2 ->this.value2 = value2if(request3){updateUI()} }request3(value1){ value3 ->this.value3 = value3if(request2){updateUI()} }
}fun updateUI()
这种同时有回调嵌套和并发流控制很容易造成数据的不同步
- 协程的写法
GlobalScope.launch(Dispatchers.Main){val value1 = request1()val deferred2 = GlobalScope.async{request2(value1)}val deferred3 = GlobalScope.async{request3(value1)}updateUI(deferred2.await(),deferred3.await())
}suspend request1()
suspend request2(..)
suspend request3(..)
协程的目的是为了让多个任务之间更好的协作,解决异步回调嵌套。能够以同步的方式编排代码完成异步工作。将异步工作像同步代码一样直观,同时它也是一个并发流程控制的解决方案
协程主要是让原来要使用“异步+回调”写出来的复杂代码,简化成看似同步写出来的方式,弱化了线程的概念(对线程操作进一步抽象)
协程的用法
引入gradle依赖
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-RC' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0-RC'
常用创建协程的方法
//创建协程时,可以通过Dispatchers.IO、Dispatchers.Main、Dispatchers.Unconfined指定协程运行的线程
val job:Job = GlobalScope.launch(Dispatchers.Main)
val deffered:Deffered = GlobalScope.async(Dispatchers.IO)
Job:协程构建函数的返回值,可以把Job看成协程对象本身,包含了对协程的控制方法
Deffered 是Job的子类,增加了await方法,能够让当前协程暂时挂起,暂停往下执行。当await方法有返回值后再恢复协程,继续往下执行
- 协程的启动
public fun CoroutineScope.launch(context: CoroutineContext = EmptyCoroutineContext,start: CoroutineStart = CoroutineStart.DEFAULT,block: suspend CoroutineScope.() -> Unit
): Job {val newContext = newCoroutineContext(context)val coroutine = if (start.isLazy)LazyStandaloneCoroutine(newContext, block) elseStandaloneCoroutine(newContext, active = true)coroutine.start(start, coroutine, block)return coroutine
}
CoroutineContext - 可以理解为协程的上下文,是一种key-value数据结构
CoroutineStart - 启动模式,默认是DEFAULT,也就是创建就启动
模式 | 说明 |
---|---|
DEFAULT | 默认模式,创建即启动协程,可随时取消 |
ATOMIC | 自动模式,同样创建即启动,但启动前不可取消 |
LAZY | 延迟启动模式,只有当调用start方法时才会启动 |
协程挂起、恢复原理
挂起函数
被关键字
suspend
修饰的方法在编译阶段,编译器会修改方法的返回值、修饰符、入参、方法实现。协程的挂起是靠挂起函数中代码的实现suspend fun request(): String {delay(2 * 1000L)println("after delay")return "result from request1"} /** * 被suspend修饰的request方法经过反编译后稍加修饰后的代码 */ public static final Object request(Continuation completion) {//增加了Continuation参数,返回值变成了ObjectContinuationImpl requestContinuation = completion;if((completion.label & Integer.MIN_VALUE) == 0) {requestContinuation = new ContinuationImpl(completion) {@OverrideObject invokeSuspend(Object o){//会通过DelayKt.delay传入的ContinuationImpl经过resumeWith回调到这。协程被恢复label |= Integer.MIN_VALUE;//重新给label赋值,以便继续执行协程被挂起之后未运行的代码return request(this);}};}switch (requestContinuation.label) {case 0: {requestContinuation.label = 1;Object delay = DelayKt.delay(2000,requestContinuation);if (delay == COROUTINE_SUSPENDED){//协程被挂起return COROUTINE_SUSPENDED;}}}System.out.println("after delay")//执行协程被挂起之后的代码return "result from request1" }
协程的挂起与恢复
协程的核心是挂起、恢复。挂起、恢复的本质是return & callback 回调
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FLaPw8Pd-1641110474368)(image-20211209174241999.png)]
- 代码原理剖析
示例代码
object CoroutineScene2 { private val TAG: String = "CoroutineScene2" suspend fun request2(): String { delay(2*1000) Log.e(TAG, "request2 completed") return "result from request2" }}
上述代码反编译结果如下:
public final class CoroutineScene2 { private static final String TAG; @NotNull public static final CoroutineScene2 INSTANCE; public final Object request2(@NotNull Continuation var1) { Object $continuation; label20: { if (var1 instanceof <undefinedtype>) {//判断continuation是否被封装过ContinuationImpl,避免协程恢复的时候重复封装 $continuation = (<undefinedtype>)var1; if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) { ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE; break label20; } } $continuation = new ContinuationImpl(var1) {//将Continuation包装成ContinuationImpl并添加invokeSuspend // $FF: synthetic field Object result; int label; @Nullable public final Object invokeSuspend(@NotNull Object $result) { this.result = $result; this.label |= Integer.MIN_VALUE; return CoroutineScene2.this.request2(this);//协程恢复执行 } }; } Object $result = ((<undefinedtype>)$continuation).result; Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();//var4赋值成COROUTINE_SUSPENDED switch(((<undefinedtype>)$continuation).label) {//label默认值是0 case 0: ResultKt.throwOnFailure($result); ((<undefinedtype>)$continuation).label = 1;//将label赋值成1 if (DelayKt.delay(2000L, (Continuation)$continuation) == var4) {//DelayKt是异步IO操作会返回COROUTINE_SUSPENDED导致协程挂起 return var4;//request2执行结束,协程挂起 } break; case 1: ResultKt.throwOnFailure($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } Log.e(TAG, "request2 completed"); return "result from request2"; }}
request2 在添加 suspend 关键字之后编译器会给方法的参数增加一个 Continuation 参数,并且返回值变成了Object,方法实现也做了变化,这个过程被称为:CPS 转换(Continuation-Passing-Style Transformation)。request2 调用进来之后会将传进来的Continuation包装成ContinuationImpl并且添加了invokeSuspend方法,这个方法会在ContinuationImpl调用resumeWith恢复协程的时候被回调。
Continuation封装完之后会调用 DelayKt.delay,因为delay会返回COROUTINE_SUSPENDED将协程挂起,所以request执行到这个地方就return结束了。后面DelayKt在经过2秒的延时之后会通过传进来的ContinuationImpl 调用resumeWith,resumeWith又会调用invokeSuspend,最终将协程恢复。在invokeSuspend中又重新调用request2,将协程被挂起之后的代码继续执行。
总结
什么是协程
协程是一种解决嵌套、并发弱化线程概念的解决方案。能让多个任务之间更好的协作,能够以同步的方式编排代码完成异步工作。将异步代码写的像同步代码一样直观
协程与线程的区别是什么?
协程基于线程,但相对于线程轻量很多,可理解为在用户层模拟线程操作。
每创建一个协程,都有一个内核态线程动态绑定,用户态下实现调度、切换,真正执行任务的还是内核线程。
线程的上下文切换都需要内核参与,而协程的上下文切换,完全由用户去控制,避免了大量的中断参与,减少了线程上下文切换与调度消耗的资源。
线程是操作系统层面的概念,协程是语言层面的概念线程与协程最大的区别在于:线程是被动挂起恢复,协程是主动挂起恢复
协程的启动
根据创建协程指定的调度器HandlerDispacher,DefaultScheduler,UnconfinedDispatcher 来执行任务,以决定协程中的代码块运行在哪个线程上。
协程的挂起和恢复
本质是方法的挂起、恢复。本质是return+callback
用编译时的变换处理方法间的callback,这样可以很直观的写顺序执行的异步代码。
suspend修饰的方法一定会让协程挂起吗?
只要被
suspend
修饰的函数都是挂起函数,但是不是所有挂起函数都会被挂起。只有当挂起函数里包含异步操作时,它才会被真正挂起。由于suspend
修饰的函数,既可能返回CoroutineSingletons.COROUTINE_SUSPENDED
,表示挂起;也可能返回同步运行的结果,甚至可能返回 null,所以被suspend
修饰的函数的返回值会被编译器改成Object,以适配所有返回值类型
探秘Kotlin协程机制相关推荐
- Kotlin协程:挂起与恢复原理逆向刨析
前言:只有在那崎岖的小路上不畏艰险奋勇攀登的人,才有希望达到光辉的顶点. --马克思 前言 经过前面两篇协程的学习,我相信大家对协程的使用已经非常熟悉了.本着知其然更要知其之所以然的心态,很想知道它里 ...
- kotlin协程硬核解读(5. Java异常本质协程异常传播取消和异常处理机制)
版权声明:本文为openXu原创文章[openXu的博客],未经博主允许不得以任何形式转载 文章目录 1. 异常的本质 1.1 操作系统.程序.JVM.进程.线程 1.2 异常方法调用栈 1.3 ja ...
- 一个小故事讲明白进程、线程、Kotlin 协程到底啥关系?
前言 协程系列文章: 一个小故事讲明白进程.线程.Kotlin 协程到底啥关系? 少年,你可知 Kotlin 协程最初的样子? 讲真,Kotlin 协程的挂起/恢复没那么神秘(故事篇) 讲真,Kotl ...
- Kotlin 协程 + Spring webflux 开发后端
前言 后端响应式是未来,吞吐量会更大,而资源占用更少,其用到了类似Android系统的Loop(事件循环)机制,而协程可以减少线程等待的消耗,并且同步式的编程方式使代码可读性更高,两个仿佛天生就是一对 ...
- android 协程,Android 上的 Kotlin 协程
协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码.协程是在版本 1.3 中添加到 Kotlin 的,它基于来自其他语言的既定概念. 在 Android 上,协程有助于 ...
- 一文看透 Kotlin 协程本质
前言 公司开启新项目了,想着准备亮一手 Kotlin 协程应用到项目中去,之前有对 Kotlin 协程的知识进行一定量的学习,以为自己理解协程了,结果--实在拿不出手! 为了更好的加深记忆和理解,更全 ...
- 深入理解Kotlin协程suspend工作原理(初学者也能看得懂)
1. 概述 挂起函数是Kotlin协程最重要的一个特性,所有其他概念都建立在它的基础上.所以我们需要深入了解它的工作原理. 挂起协程意味着在中间停止它.这类似于玩游戏,当我们想暂停游戏时,可以先存档, ...
- Kotlin协程实现原理
前言 本篇解析Kotlin/JVM中的协程的实现原理. 初看suspend关键字 下面的例子模拟一个网络请求: class Temp {suspend fun fetchData(argument: ...
- Kotlin 协程,怎么开始的又是怎么结束的?原理讲解!
九心 | 作者 承香墨影 | 校对 https://juejin.cn/post/6862548590092140558 | 原文 Hi,大家好,这里是承香墨影! 上周我们聊到 Kotlin 协程的使 ...
最新文章
- swift 中showAlertTitle的使用
- 互联网企业烧钱抢占公交Wi-Fi市场
- 显示服务器图片url,服务器上图片的url地址
- 明明有了 promise ,为啥还需要 async await ?
- java hook 框架_hook框架-frida简单使用模板以及frida相关接口
- Ajax里的onreadystatechange的作用
- linux 限制单个ip流量,centos 的單ip流量控制-CentOS下利用iptables限速及限制每IP連接數...
- git基本使用知识点总结
- OneDrive更换要同步文件夹
- 思科网络安全解决方案
- 计算机的正确使用方法,电脑开关机的正确的操作步骤顺序(不会对电脑造成任何损坏)...
- 现代大学英语精读第二版(第六册)学习笔记(原文及全文翻译)——3 - What Is News?(新闻是什么?)
- matlab中if语句的条件,matlabif条件语句
- win2008服务器虚拟内存设置,电脑虚拟内存设置(Win 7/8/10、Windows Server 2003 - 2019)...
- 读《Ideal MHD》(1)-磁流体力学方程组推导
- 搭档之家|新零售:社交电商的光明尽头
- 非系统APK很多权限受限制,如何让APK成为系统APK
- day2 编码与基本数据类型转换
- 过去的一切该翻篇了 好好奔向未来吧
- 免费获取百度网盘会员一天的不限速网盘会员