本篇文章解析Kotlin协程的CoroutineScope, CoroutineContext及其继承类, 旨在探讨并理解kotlin的协程使用,以及对各个协程api细节整理。
关于协程跨平台的实现以及跨平台使用介绍,欢迎去我的博客:编译之后--Kotlin 跨平台协程看看,或者去看看官方的视频&ppt,这也是我本篇文章的基础:
- Presentations and videos:
- Introduction to Coroutines (Roman Elizarov at KotlinConf 2017, slides)
- Deep dive into Coroutines (Roman Elizarov at KotlinConf 2017, slides)
- Kotlin Coroutines in Practice (Roman Elizarov at KotlinConf 2018, slides)
这篇文章很久前就已经打好了草稿,但是由于最近忙没有时间写,再加上最近都没有在写Kotlin。今天仔细又读了一些文档、源码,为了整理一下自己的思路,将自己的个人理解写在这里。
本文的前提是读者了解协程基本使用,见官方中文文档
注:Kotlin版本迭代日新月异,本篇文章难免过时或有疏漏,请读者注意。
CoroutineScope
什么是CoroutineScope?
CoroutineScope可以理解为协程的作用域
,可以管理其域内的所有协程。一个CoroutineScope可以有许多的子scope。
创建子scope的方式有许多种,常见的有:
- 使用lauch, async 等builder创建一个新的子协程。协程(AbstractCoroutine)继承了 CoroutineScope,从父scope中继承了协程上下文(见下文CoroutineContext) 以及Job(见下文)
- 使用coroutineScope Api创建新scope:
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R
这个api主要用于方便地创建一个子域,并且管理域中的所有子协程。注意这个方法只有在所有 block中创建的子协程全部执行完毕后,才会退出。如下示例:
与之类似的还有supervisorScope,区别是supervisorScope 在子协程失败时不影响其他子协程,而coroutineScope是将异常抛出。// print输出的结果顺序将会是 1, 2, 3, 4 coroutineScope { delay(1000) println("1") launch { delay(6000) println("3") } println("2") return@coroutineScope } println("4")
- 继承CoroutineScope.这也是比较推荐的做法,用于处理具有生命周期的对象。
class SomethingWithLifecycle : CoroutineScope {
// 创建一个Job,并用这个job来管理你的SomethingWithLifecycle的所有子协程
private val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
// 当生命周期销毁时,结束所有子协程
fun close() { job.cancel() }
}
CoroutineContext
这里我将CoroutineContext翻译为协程上下文。 我也找不到更好的词去翻译它。协程上下文包含当前协程scope的信息, 比如的Job, ContinuationInterceptor, CoroutineName 和CoroutineId。在CoroutineContext中,是用map来存这些信息的, map的键是这些类的伴生对象,值是这些类的一个实例。 这个设计也是蛮骚的, 导致你可以这样子取得context的信息:
val job = context[Job]
val continuationInterceptor = context[ContinuationInterceptor]
每一个信息(Element)的继承关系大概是这样的, 我拿Job来举例:
Job.Key继承了CoroutineContext.Key, Job 继承了CoroutineContext.Element,而CoroutineContext.Element继承了 CoroutineContext。
name 和 id都无关紧要,我们仔细讲讲Job和ContinuationInterceptor。
Job
如上文提到的,Job继承了CoroutineContext.Element, 他是协程上下文的一部分。 Job有两个重要的子类实现:JobSupport,提供Job 的父子关系管理, 和AbstractCoroutine,即协程。
Job对象持有所有的子job实例,可以取消所有子job的运行。Job的join方法会等待自己以及所有子job的执行, 所以Job给予了CoroutineScope一个管理自己所有子协程的能力。
JobSupport
JobSupport这边的api已经被标记为废弃了,将来可能会有变动。其实这边api设计我感觉有点看不懂,十分地confusing。以后看看有没有变化吧,这部分主要是提供一个 工厂方法 Job()
,可以为你的组件提供一个初始的context, 一个JobImpl的实例。JobSupport同时是AbstractCoroutine的父类。
AbstractCoroutine
讲讲Job最重要的子类: AbstractCoroutine。这个类就可以直接理解为是一个协程对象了,他继承了CoroutineScope, Job, Continuation, JobSupport。
常见地, 使用launch 或者async方法都会实例化出一个AbstractCoroutine 的协程对象,而这个协程对象因为继承了CoroutineScope,所以拥有一个协程上下文。 一个协程的协程上下文的Job值就是他本身,即:
val j = launch { delay(6000) }
// True!
println(j[Job] == j)
而关于Continuation,是kotlin内部用来跳转恢复协程用的。这个东西对外部暴露的东西不多,我们需要了解的有:
- suspendCoroutine, 挂起当前协程,利用Continuation继续协程的执行,常用于与其他平台阻塞方法兼容,这是1.1的api,不多做介绍
- SequenceBuilderIterator,这个就是kotlin协程的生成器了,详见官方文档
ContinuationInterceptor
这个接口主要是由各种dispather实现。 见官方文档
肚子好饿,不详细介绍了,后面特别补上一篇文章细说dispatcher在各个平台的表现吧, 敬请期待。
The end
最后推荐个神仙仓库吧, kotlin的各种提案,
在这里面可以看到各种kotlin特性设计的方案。比如协程的:
https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md#generators
如果觉得本篇文章对您有所帮助, 点个赞吧~~蟹蟹~
网友评论