kotlin协程的理解

伴生对象:companion object 其实质等同于Java中的单例模式

协程:通常实现是用户态的任务协作式调度

  1. 一段可执行代码
  2. 可挂起/可恢复执行
  3. 概念上与语言无关,协程这个概念于1958年提出

依赖框架:

协程的启动:

1.协程体:协程中要执行的操作,是一个被suspend修饰的lambda表达式

传递末尾的lambda表达式,在Kotlin中有一个约定:

如果函数的最后一个参数是函数,那么作为相应的参数的传入的lambda表达式可以放在圆括号之外

2.协程体类:编译器会将协程体编译成封装协程体操作的匿名内部类

3.协程构建器:用于构建协程的函数:比如launch,async

4.挂起函数:由suspend修饰的函数,挂起函数只能在挂起函数或者协程体中调用,

5.挂起点:一般对应挂起函数被调用的位置

6.续体:Continuation

CoroutineScope:

  1. 是一个接口
  2. 只有一个属性:CoroutineContext(协程上下文),
  3. 是一个作用范围,可以通过CoroutineScope的扩展函数去创建协程(launch async),当这个作用范围被取消的时候,其内部协程也会被取消;GlobalScope除外
  • launch函数返回一个Job,可以通过Job进行管理协程
  • 为协程提供一个上下文CoroutineContext

GlobalScope:

  • 实现CoroutineScope接口,并且重写了上下文,返回一个EmptyCoroutineContext
  • 由object修饰,是一个单例对象,所以生命周期跟随整个应用,无法通过自身取消内部协程

launch函数的三个参数,也就是启动协程的三要素:

  • CoroutineContext 协程的上下文
  • CoroutineStart 协程的启动模式
  • suspend CoroutineScope.() -> Unit 协程体

CoroutineContext 协程上下文 ,这是一个数据集合接口声明,所包含的元素有:

  • 协程中Job
  • 调度器:CoroutineDispatcher
  • 协程名:CoroutineName
  • ...

CoroutineContext 的数据结构:链表

CombinedContext是CoroutineContext的一个实现类,也是链表中的具体实现节点,节点包含两个元素,

  • element:当前的节点集合元素,
  • left :CoroutineContext类型,指向链表的下一个元素

第四行:函数get,一个由operator修饰的操作符重载,对应"[ ]"操作符,通过key获取Element对象

第七行:函数fold,遍历当前集合的每一个Element,并对每一个元素进行opreator操作,将操作后的结果进行累加,以initial为其实开始累加,最终返回一个新的CoroutineContext 上下文

第十六行:函数plus,由operator修饰操作符重载,对应"+"操作符,合并两个CoroutineContext对象中的元素(这个元素可以是实现CoroutineContext 接口的任何对象:Job、CoroutineDispatcher、CoroutineName等等),将合并后的上下文返回,

从函数Plus中,我们可以清晰的看出,CoroutineContext的数据存储方式是一个链表,链表的每个节点是CombinedContext,并且存在拦截器的情况下,拦截器永远是链表的头结点 ,拦截器使用效率很高,这样可以保证更快的读取到拦截器

每个元素在创建的时候都会生成唯一的Key对象(单例模式),所以元素在添加到集合中时(plus)同类元素都会被最后的一个所覆盖

如果存在想相同的key的Element对象,则对其进程"覆盖"(先从集合中移除要plus的Element对象,返回一个移除后的集合,确保当前集合不包含要添加的元素)

以CombinedContext中的minusKey进行理解:

1.当我们在plus一个CoroutineContext元素时,需要对当前的CoroutineContext集合进行移除操作,

2.由于Key的唯一性,链表中不会存在重复的元素结点!首先从头结点的element中根据要添加元素的key进行查询,如果查询结果不为空,则直接返回left(意味着在这个链表存在要添加的元素,并且是当前结点的element,故而可以直接返回left)

3.如果2中的查询结果为空,则继续调用minusKey(递归)直到满足以下条件,退出递归

  • 移除结点后与移除前的left一样,那么也就意味着链表中不存在要添加的element,所以直接 返回这个链表
  • 移除结点后,链表为空,意味着当前链表只有一个结点,并且该结点中的element与要添加的一样,那么直接返回当前的节点的element
  • 当前链表就是一个空链表,那么将第三行代码中的newleft和elememt重新组合

Element:

1.第40行代码:每一个元素的类型是Element,而它又实现了CoroutineContext接口,所以Element即可以是一个集合中的元素,也可以是一个集合

CoroutineStart 是协程的启动模式,存在以下4种模式:

  • DEFAULT 立即调度,可以在执行前被取消
  • LAZY 需要时才启动,需要start、join等函数触发才可进行调度
  • ATOMIC 立即执行,执行前不可以被取消
  • UNDISPATCHED 立即在当前线程执行,直到遇到第一个挂起点(可能切线程)

协程体:suspend CoroutineScope.() -> Unit

一个lambda表达式,也就是协程中要执行的代码块,即launch函数的代码块;

为什么使用CoroutineScope扩展函数?

上面讲到,在CoroutineScope中只有一个属性,那就是协程上下文;这样我们可以在协程体中访问协程上下文这个对象

-----------------------------------协程中线程的挂起 和 切换----------------------------------------

Dispatchers:调度器,是协程中提供的线程调度器,用来切换线程,指定协程所运行的线程

源码分析:

DisPatchers中提供了4种类型的调度器:

  • Defaul:默认调度器,适合CPU密集型任务调度器,比如逻辑计算;
  • Main:UI调度器;
  • Unconfind:无限制(无拘束)调度器,对协程执行的线程不做限制,协程恢复时可以在任意线程;
  • IO:IO调度器,适合IO密集型任务调度器,比如读写文件,网络请求;

从源码中可以看到,这4种类型的调度器的类型均是:CoroutineDispatchers

CoroutineDispatchers:

继承自AbstractCoroutineContextElement,而AbstractCoroutineContextElement是Element的一个抽象实现类,所以调度器本身也是一个CoroutineContext,也可以存放在CoroutineContext集合中;同时实现了ContinuationInterceptor,一个拦截器接口

1.在上图代码中可以看到:ContinuationInterceptor实现了CoroutineContext.Element接口,所以拦截器也可以作为CoroutineContext集合的一个元素

2.在ContinuationInterceptor中定义了一个伴生对象Key,它的类型是CoroutineContext.Key,作为CoroutineContext集合元素的索引的理由:

  • 伴生对象的唯一性
  • 通过类型访问集合元素,更直观

3.interceptContinuation:对协程体类对象continuation的一次包装,并返回一个新的Continuation,

CoroutineDispatcher:继承自AbstractCroutineContextElement,同时实现了拦截器接口;

  • 说明了调度器的本质也是一个拦截器,在kotlin中所有的调度器都是继承自它来实现的自身调度逻辑
  • 调度器同时也可以作为CroutineContext集合中的元素

1.isDispatchNeeded:是否需要线程调度;

2.dispatch:线程调度,让一个runnable对象在指定的线程运行;

3.interceptContinuation:将协程体类对象包装成一个DispatchedContinuation对象;

DispatchedContinuation:使用线程调度器将协程体调度到指定的线程执行

1.实现了续体:Continuation,重写了resumeWith的内部实现逻辑,并且持有线程调度器;

2.有两个属性:

  • dispatcher:线程调度器
  • continuation:线程体类对象,也就是在包装成DispatchedContinuation时传入的协程体类对象

3.关注:delegate,实质就是DispatchedContinuation对象本身

4.resumeWith:首先通过isDispatchNeeded判断是否需要线程调度;

  • 如果需要线程调度,则使用dispatcher#dispatch进行调度,所需要的参数分别是:协程上下文和一个runnable对象(这里传入的this,即表示DispatchedContinuation对象本身,由于其继承自DispatchedTask,继续跟进会发现最终实现了Runnable接口),所以这个runnable会运行在调度的线程上
  • 如果不需要调度,则使用resumeWith,

5.runnable:从下面源码中可以看到,在run方法中首先从delegate中取出协程体对象,然后调用协程体的扩展函数resume,实质还是执行resumeWith

Dispatchers.Default默认调度器

dispatcher#dispatch()的实现是在调度器的具体实现类中,我们以Dispatchers.Default进行分析

1.useCoroutinesScheduler:默认情况是ture,所以会构建一个DefaultScheduler

2.IO调度器是Dispatchers.Default内的一个变量,并且它和Default调度器共享CoroutineScheduler线程池。

3.调度器的核心是重写dispatch()进行线程的切换,追溯到父类的dispatch:

ExperimentalCoroutineDispatcher

1.在ExperimentalCoroutineDispatcher中的dispatch的实现是通过调用coroutineScheduler.dispatch(),

2.CoroutineScheduler是一个Kotlin实现的线程池,提供协程运行的线程。

-----------------------------------协程中Worker线程----------------------------------------

Worker存在5种状态:

  • CPU_ACQUIRED 获取到cpu权限
  • BLOCKING 正在执行IO阻塞任务
  • PARKING 已处理完所有任务,线程挂起
  • DORMANT 初始态
  • TERMINATED 终止态
  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值