什么是协程

  • 场景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协程机制相关推荐

  1. Kotlin协程:挂起与恢复原理逆向刨析

    前言:只有在那崎岖的小路上不畏艰险奋勇攀登的人,才有希望达到光辉的顶点. --马克思 前言 经过前面两篇协程的学习,我相信大家对协程的使用已经非常熟悉了.本着知其然更要知其之所以然的心态,很想知道它里 ...

  2. kotlin协程硬核解读(5. Java异常本质协程异常传播取消和异常处理机制)

    版权声明:本文为openXu原创文章[openXu的博客],未经博主允许不得以任何形式转载 文章目录 1. 异常的本质 1.1 操作系统.程序.JVM.进程.线程 1.2 异常方法调用栈 1.3 ja ...

  3. 一个小故事讲明白进程、线程、Kotlin 协程到底啥关系?

    前言 协程系列文章: 一个小故事讲明白进程.线程.Kotlin 协程到底啥关系? 少年,你可知 Kotlin 协程最初的样子? 讲真,Kotlin 协程的挂起/恢复没那么神秘(故事篇) 讲真,Kotl ...

  4. Kotlin 协程 + Spring webflux 开发后端

    前言 后端响应式是未来,吞吐量会更大,而资源占用更少,其用到了类似Android系统的Loop(事件循环)机制,而协程可以减少线程等待的消耗,并且同步式的编程方式使代码可读性更高,两个仿佛天生就是一对 ...

  5. android 协程,Android 上的 Kotlin 协程

    协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码.协程是在版本 1.3 中添加到 Kotlin 的,它基于来自其他语言的既定概念. 在 Android 上,协程有助于 ...

  6. 一文看透 Kotlin 协程本质

    前言 公司开启新项目了,想着准备亮一手 Kotlin 协程应用到项目中去,之前有对 Kotlin 协程的知识进行一定量的学习,以为自己理解协程了,结果--实在拿不出手! 为了更好的加深记忆和理解,更全 ...

  7. 深入理解Kotlin协程suspend工作原理(初学者也能看得懂)

    1. 概述 挂起函数是Kotlin协程最重要的一个特性,所有其他概念都建立在它的基础上.所以我们需要深入了解它的工作原理. 挂起协程意味着在中间停止它.这类似于玩游戏,当我们想暂停游戏时,可以先存档, ...

  8. Kotlin协程实现原理

    前言 本篇解析Kotlin/JVM中的协程的实现原理. 初看suspend关键字 下面的例子模拟一个网络请求: class Temp {suspend fun fetchData(argument: ...

  9. Kotlin 协程,怎么开始的又是怎么结束的?原理讲解!

    九心 | 作者 承香墨影 | 校对 https://juejin.cn/post/6862548590092140558 | 原文 Hi,大家好,这里是承香墨影! 上周我们聊到 Kotlin 协程的使 ...

最新文章

  1. swift 中showAlertTitle的使用
  2. 互联网企业烧钱抢占公交Wi-Fi市场
  3. 显示服务器图片url,服务器上图片的url地址
  4. 明明有了 promise ,为啥还需要 async await ?
  5. java hook 框架_hook框架-frida简单使用模板以及frida相关接口
  6. Ajax里的onreadystatechange的作用
  7. linux 限制单个ip流量,centos 的單ip流量控制-CentOS下利用iptables限速及限制每IP連接數...
  8. git基本使用知识点总结
  9. OneDrive更换要同步文件夹
  10. 思科网络安全解决方案
  11. 计算机的正确使用方法,电脑开关机的正确的操作步骤顺序(不会对电脑造成任何损坏)...
  12. 现代大学英语精读第二版(第六册)学习笔记(原文及全文翻译)——3 - What Is News?(新闻是什么?)
  13. matlab中if语句的条件,matlabif条件语句
  14. win2008服务器虚拟内存设置,电脑虚拟内存设置(Win 7/8/10、Windows Server 2003 - 2019)...
  15. 读《Ideal MHD》(1)-磁流体力学方程组推导
  16. 搭档之家|新零售:社交电商的光明尽头
  17. 非系统APK很多权限受限制,如何让APK成为系统APK
  18. day2 编码与基本数据类型转换
  19. 过去的一切该翻篇了 好好奔向未来吧
  20. 免费获取百度网盘会员一天的不限速网盘会员

热门文章

  1. RTX51Tiny 学习笔记(一)
  2. 两台笔记本组建无线局域网共享上网
  3. 2023年全国最新会计专业技术资格精选真题及答案1
  4. A trip to InterSpeech2012 (Part II)
  5. 如何查询个人电脑的最大支持内存?
  6. 关于python爬虫爬取小说
  7. 年薪百万的年轻人都是怎样生活的?——脸书程序员的故事
  8. 3B再战:360又一次挟持了用户
  9. xml文件c语言读取函数,IDL读取XML文件
  10. 【牛客网】星际密码(斐波那契数列)