PS:B站也有我的视频分享,内容和本文有重叠也有不同,结合来看效果更好喔:Android 深入理解协程 BaguTree周六茶话
(内容略长,前三点是重点,四五六点建议结合源码观看)
一、Continuation Passing Style (CPS)
协程在多种语言中都有应用,那它们通用的指导概念是什么?那就是CPS
1、原理
(1)举个例子,平时使用的直接编码风格Direct Style
// 三个步骤,发送一个token
fun postItem(item: Item) {
val token = generateToken() // step1
val post = createPost(token, item) // step2
processPost(post) // step3
}
CPS的 C 是Continuation,是续体的意思,上面代码中 step2、step3 是 step1 的续体,step3 是 step2 的续体
(2)将上述代码进行CPS转化后,变成传接续体风格Continuation Passing Style
fun postItem(item: Item) {
generateToken { token -> // setp1
createPost(token, item) { post -> // step2
processPost(post) // step3
}
}
}
这个变化后可以发现,CPS转化其实是将下一步变成了回调。结论:CPS == Callbacks
(3)Kotlin 提供了一种Direct Style的写法,而且同时能实现CPS的效果。为什么能这么实现?因为 kotlin在编译的时候替我们实现了回调。看个例子,我们将上面的例子写成kotlin的代码:
suspend fun postItem(item: Item) {
val token = generateToken() // 在上面的例子中需要传送续体(callback),所以这个方法也是 suspend 方法
val post = createPost(token, item) // 也是 suspend 方法
processPost(post)
}
将上述代码编译成Java后,会发现带suspend的方法 在编译后方法签名都会增加一个续体参数,下面对比一下
// 编译前:Kotlin
suspend fun createPost(token: Token, item: Item): Post { … }
// 编译后:Java/JVM(cont其实是一个callback,Post为结果)
Object createPost(Token token, Item item, Continuation<Post> cont) { … }
回到上面的举例代码,其中前两步每一步都会产生结果,而每一步的结果都需要传递给下一步,所以可以思考一下,续体的作用是什么?
总结:续体负责将当前步骤结果传递给下个步骤,同时移交下一步的调用权
调用权是怎么产生的?是因为我们将下一步的代码打包成了对象,继续传递下去,所以继续执行下一步的调用权会移交给后面执行的代码(这才是异步执行的核心思想)。
2、续体在kotlin中的声明
下面是Kotlin中续体接口,看下这个接口的注释,suspension point就是标注suspend的方法

这个接口内有一个对象和一个方法:
- CoroutineContext:是一个链表结构,可以使用「+」操作符,其中包含协程执行时需要的一些参数:名称/ID、调度器 Dispatcher、控制器 Job、异常 Handler等(把 Job 称为「控制器」感觉好理解一些)
- resumeWith:触发下一步的方法,参数 result 是当前步骤的结果(上面说到了
调用权,resumeWith就是调用入口)
「Continuation is a generic callback interface」: Continuation是一个续体通用的回调接口
二、State Machine
状态机是一个可以控制状态的对象
续体会缓存结果递交给下一步,状态机会缓存步骤编号,并在每一步触发的时候将状态改为下一步,以实现步骤切换
1、续体+状态机
kotlin中的suspend方法最终会被编译成一个状态机,下面举个例子,来看看suspend方法是如何转换的
(1)我们先给每个步骤编号:
suspend fun postItem(item: Item) {
switch (label) {
case 0:
generateToken()
case 1:
createPost(token, item)
case 2:
processPost(post)
}
}
(2)然后进行CPS转换,加上续体,实现结果传递、调用权转移;同时看到转换后有个「resume」方法,每一次触发下一步都会调用该方法,同时在「postItem」内实现状态机,保证每次调用会触发下一步骤
// 入口:postItem
fun postItem(item: Item, cont: Continuation) {
val sm = object : CoroutineImpl(cont) { // cont是父协程的续体,在当前协程结束时会触发cont的resume
fun resume(...) {
postItem(null, this) // 续体回调入口
}
}
switch (sm.label) {
case 0:
sm.item = item // 初始参数,执行过程中传递的一些参数
sm.label = 1 // 状态控制
requestToken(sm) // step1,传入续体,执行完成后调用resume,并将结果传递下去
case 1:
val token = sm.result as Token // 从续体中拿取上一步的结果
val item = sm.item // 从续体中拿取初始参数
sm.label = 2 // 状态控制
createPost(token, item, sm) // step2,传入续体&参数,执行完成后调用resume,并将结果传递下去
case 2:
val post = sm.result as Post // 从续体中拿取上一步的结果
processPost(post, sm) // spte3
}
}
仔细看上面的额注释,想象一下执行过程,首先进入「case 0」然后「label」变成1,在「requestToken」方法内调用一次「sm」对象的「resume」,再次进入「postItem」,创建新的「sm」,此时假设新的「sm」会继承「cont」的数据,那么会进入「case 1」......
再复习一下:
续体传递结果,移交下一步调用权
状态机实现每调用一次同一个方法,就执行一个步骤
2、这两个东西结合可以实现什么?
有了续体&状态机我们可以做什么?
| ╮( ̄▽  ̄)╭ 「在任意的时候发起下一步」 |
|---|
想象一下,如果我只会调用「resumeWith」...... 没关系,续体&状态机帮我们完成了一切(参数传递、步骤切换),我们只用在任意时刻任意线程中调用「resumeWith」来触发下一步
三、launch执行过程
1、整体流程
明白什么是续体、状态机后,我们来看一下协程发起的过程,从CoroutineScope.launch看起:

源码就不一一截图了,整体流程如下图,从左上角开始:

上图浅红色的是重点,后续会重点展开讲createCoroutineUnintercepted的反编译的代码,先看一下上图的几个关键对象:
(1)SuspendLambda
SuspendLambda是一个续体的实现,下面是SuspendLambda继承关系,可以看到它是已经实现了resumeWith方法了

(2)Dispatcher
Dispatcher是续体的分发器,它有多种实现,最简单的就是Dispatchers.Main,我们也从这个源码开始看起。我们先来看下继承关系,可以先简单的认为,各类Dispatcher的爷爷就是ContinuationInterceptor,爹就是CoroutineDispatcher

上图中有两种Dispatcher,其中HandlerContext的dispatch实现会回调到主线程:

(3)DispatchedContinuation
DispatchedContinuation也是续体,不过它是一个续体的委托类,内部持有一个续体对象。来看一下DispatchedContinuation的继承关系,SchedulerTask在run方法中调用「resume相关方法 & 异常Handler」

因为续体的resumeWith都交由DispatchedContinuation的run方法调用,所以会称之为续体的委托控制者

(看到这个by是不是有一种恍然大悟的感觉,可读性大大提升)
2、反编译createCoroutineUnintercepted方法
其他方法基本都有源码可看,就这个方法隐藏的特别深,最终找到kotlin开源项目中的代码:kotlin/IntrinsicsJvm.kt at master · JetBrains/kotlin (github.com)

来看下这个方法的说明:
-
Creates unintercepted coroutine with receiver type [R] and result type [T]. 使用receiver和result的类型创建一个不可打断的协程(receiver可以忽略,关注result即可)
-
This function creates a new, fresh instance of suspendable computation every time it is invoked. 这个方法会创建suspend相关逻辑(核心逻辑实际上是编译器创建的,这里面只执行一个new)
-
To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance. 通过resume方法启动协程
-
The [completion] continuation is invoked when coroutine completes with result or exception. completion是一个续体,在这里创建的协程结束或者异常时会被调用
先说一个结论,图中的「this」是一个继承BaseContinuationImpl的对象,为什么呢,接着看下面反编译分析
3、协程创建执行过程分析
(1)模拟launch过程发起协程
因为直接反编译标准协程代码并不能非常直接的看到调用过程,所以我们根据createCoroutineUnintercepted的解释发起协程
import kotlinx.coroutines.delay
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.createCoroutineUnintercepted
fun main() {
launch3 {
print("before")
delay(1_000)
print("\nmiddle")
delay(1_000)
print("\nafter")
}
Thread.sleep(3_000)
}
fun <T> launch3(block: suspend () -> T) {
// 1、传入代码块block,使用block创建协程,
// 2、同时自行创建一个续体,「resumeWith」最终会被调用
val coroutine = block.createCoroutineUnintercepted(object : Continuation<T> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
override fun resumeWith(result: Result<T>) {
println("\nresumeWith=$result")
}
})
// 3、执行block协程
coroutine.resume(Unit)
}
确认执行结果:
before
middle
after
resumeWith=Success(kotlin.Unit)
(2)反编译查看创建过程
以上操作主要为了能更清晰的看到反编译的代码,我们使用dx2jar进行反编译,看以下代码中注释的1-3步
import kotlin.Metadata;
import kotlin.Result;
import kotlin.ResultKt;
import kotlin.Unit;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;
import kotlin.coroutines.EmptyCoroutineContext;
import kotlin.coroutines.intrinsics.IntrinsicsKt;
import kotlin.coroutines.jvm.internal.DebugMetadata;
import kotlin.coroutines.jvm.internal.SuspendLambda;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.internal.Intrinsics;
import kotlinx.coroutines.DelayKt;
public final class KTest6Kt {
public static final void main() {
// 1、编译后,代码块变为继承「SuspendLambda」的对象,该对象还实现了Function接口,是一个「续体+状态机」的对象
launch3(new KTest6Kt$main$1(null));
Thread.sleep(3000L);
}
public static final <T> void launch3(Function1<? super Continuation<? super T>, ? extends Object> paramFunction1) {
Intrinsics.checkNotNullParameter(paramFunction1, "block");
// 2、「createCoroutineUnintercepted」会创建一个新的「KTest6Kt$main$1」对象(为什么?),传入我们自定义的续体
Continuation continuation = IntrinsicsKt.createCoroutineUnintercepted(paramFunction1, new KTest6Kt$launch3$coroutine$1());
Unit unit = Unit.INSTANCE;
Result.Companion companion = Result.Companion;
// 3、启动协程
continuation.resumeWith(Result.constructor-impl(unit));
}
// 我们自定义的续体的内部类
public static final class KTest6Kt$launch3$coroutine$1 implements Continuation<T> {
public CoroutineContext getContext() {
return (CoroutineContext)EmptyCoroutineContext.INSTANCE;
}
public void resumeWith(Object param1Object) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("\nresumeWith=");
stringBuilder.append(Result.toString-impl(param1Object));
param1Object = stringBuilder.toString();
System.out.println(param1Object);
}
}
// 协程「续体+状态机」的内部类
static final class KTest6Kt$main$1 extends SuspendLambda implements Function1<Continuation<? super Unit>, Object> {
int label;
KTest6Kt$main$1(Continuation param1Continuation) {
super(1, param1Continuation);
}
public final Continuation<Unit> create(Continuation<?> param1Continuation) {
Intrinsics.checkNotNullParameter(param1Continuation, "completion");
return (Continuation<Unit>)new KTest6Kt$main$1(param1Continuation);
}
public final Object invoke(Object param1Object) {
return ((KTest6Kt$main$1)create((Continuation)param1Object)).invokeSuspend(Unit.INSTANCE);
}
// 4、resumeWith触发
public final Object invokeSuspend(Object param1Object) {
Object object = IntrinsicsKt.getCOROUTINE_SUSPENDED();
int i = this.label;
if (i != 0) {
if (i != 1) {
if (i == 2) {
ResultKt.throwOnFailure(param1Object);
} else {
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
} else {
ResultKt.throwOnFailure(param1Object);
param1Object = this;
System.out.print("\nmiddle");
((KTest6Kt$main$1)param1Object).label = 2;
}
} else {
ResultKt.throwOnFailure(param1Object);
param1Object = this;
System.out.print("before");
((KTest6Kt$main$1)param1Object).label = 1;
if (DelayKt.delay(1000L, (Continuation)param1Object) == object)
return object;
param1Object = this;
System.out.print("\nmiddle");
((KTest6Kt$main$1)param1Object).label = 2;
}
System.out.print("\nafter");
return Unit.INSTANCE;
}
}
}
ps:对于两个参数的重载方法,可以看到第一个参数并没有用:
其中第3步的resumeWith方法实现在哪里?我们看下上面的SuspendLambda的继承关系图即可知道是BaseContinuationImpl # resumeWith方法:
internal abstract class BaseContinuationImpl(
// 创建时传入,完成时调用。在上面的例子中,这是我们自定义的续体,在「KTest6Kt$main$1 # create」方法中传入
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
// This implementation is final. This fact is used to unroll resumeWith recursion.(展开递归)
public final override fun resumeWith(result: Result<Any?>) {
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
// unrolling:铺开,这里表示铺开递归,使用循环代替递归,减少栈深度
//「this」即block转化为的「SuspendLambda」对象,即「KTest3Kt$main$1」对象
var current = this
//「result」一般默认是「Result.success(Unit)」,上面的例子中是「Unit」
var param = result
while (true) {
// Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
// can precisely track what part of suspended callstack was already resumed
probeCoroutineResumed(current)
with(current) {
// 获取「current」的「completion」
// 在上面的例子中,「completion」是内部协程对象,即「KTest3Kt$launch$coroutine$1」对象
val completion = completion!! // fail fast when trying to resume continuation without completion
//「invokeSuspend」获取执行结果
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
// unrolling recursion via loop
// 如果用于完成的续体内还有 completion,则开始套娃,直至获取到最外层的「续体」
// (「BaseContinuationImpl」是「SuspendLambda」的父类,只有「CPS」的时候会生成)
// 「suspend」方法中有「suspend」方法时会触发这里的逻辑
current = completion
param = outcome
} else {
// top-level completion reached -- invoke and return
// 在默认情况下,最外部协程的续体是「StandaloneCoroutine」
// 上面的例子中,最外部的是我们自己声明的匿名内部续体(object : Continuation<T>)
completion.resumeWith(outcome)
return
}
}
}
}
......
}
「resumeWith」→ 「invokeSuspend」→
(1)invokeSuspend返回COROUTINE_SUSPENDED,return结束当前resumeWith,等待下次resumeWith
(2)invokeSuspend返回其他或者异常,生成Result对象,如果completion还是BaseContinuationImpl对象,则套娃,否则调用completion续体的resumeWith
这里套娃的情况出现在suspend方法中还有suspend方法,completion实际上是父协程的续体,即续体里还有续体
(3)拓展:生成SuspendLambda的方法
- kotlin/SuspendLambdaLowering.kt at master · JetBrains/kotlin (github.com)
- SuspendLambdaLowering # generateContinuationClassForLambda
- kotlin/JvmSymbols.kt at master · JetBrains/kotlin (github.com)

以上重点已讲完,下面最重要的就是那两张UML图
四、delay执行过程
对续体有深入了解后,我们再来看看下面例子中delay的执行过程
GlobalScope.launch(Dispatchers.Main) {
print("before")
delay(1_000)
print("\nmiddle")
delay(1_000)
println("\nafter")
}
delay方法:
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
// if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
if (timeMillis < Long.MAX_VALUE) {
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
}
suspendCancellableCoroutine作用:获取一个续体后,传入scheduleResumeAfterDelay方法
看一下suspendCancellableCoroutine的内容,其中通过cancellable.getResult() → trySuspend()返回COROUTINE_SUSPENDED终止当前执行,等待下次resume
COROUTINE_SUSPENDED 这个标志代表 return 当前方法,执行权力交由下次调用续体 resumeWith 的对象
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
): T =
// Obtains the current continuation instance inside suspend functions
suspendCoroutineUninterceptedOrReturn { uCont ->
// 创建「CancellableContinuationImpl」
val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
/*
* For non-atomic cancellation we setup parent-child relationship immediately
* in case when `block` blocks the current thread (e.g. Rx2 with trampoline scheduler), but
* properly supports cancellation.
*/
cancellable.initCancellability()
block(cancellable) // block内发送延迟消息,发送完后当前调用栈即可结束,即可「return」
cancellable.getResult() // 首次调用,返回「COROUTINE_SUSPENDED」,「COROUTINE_SUSPENDED」就是「return」
}
其中atomic操作开源代码:kotlinx.atomicfu/AtomicFU.kt…
回到delay方法中,其中context.delay从context中获取ContinuationInterceptor(该类是各种Dispatcher的基类),反回空就使用DefaultDelay
internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay
本例中使用的是Dispatcher.Main,这里获取的就是HandlerContext对象,可以看到HandlerContext中的scheduleResumeAfterDelay方法
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
val block = Runnable {
with(continuation) { resumeUndispatched(Unit) }
}
if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
continuation.invokeOnCancellation { handler.removeCallbacks(block) }
} else {
cancelOnRejection(continuation.context, block) // 发送失败就 cancel
}
}
使用handler.postDelayed发送延迟消息,消息体中触发CancellableContinuationImpl续体的resumeUndispatched方法
override fun CoroutineDispatcher.resumeUndispatched(value: T) {
val dc = delegate as? DispatchedContinuation
resumeImpl(value, if (dc?.dispatcher === this) MODE_UNDISPATCHED else resumeMode)
}
delegate是啥?可以看下CancellableContinuationImpl的构造方法

在前面suspendCancellableCoroutine方法中可以看到传入的是uCont.intercepted(),即当前协程的续体再转化成DispatchedContinuation对象
如果一定要看到uCont是什么,可以看到我们第一次反编译的代码delegate就是KTest6Kt$main$1对象,就是传入「delay」方法的续体,就是「SuspendLambda」
下一行中的dispatcher就是当前协程的调度器Dispatcher.Main,意思是如果resume和launch的dispatcher是同一个,则传入undispatched的状态,这里就是同一个
回来再看CancellableContinuationImpl # resumImpl,其中的操作是:如果当前是NoCompleted状态则调用dispatchResume方法

调用过程如下:
CancellableContinuationImpl # resumImpl
↓ // NotCompleted
CancellableContinuationImpl # dispatchResume
↓ // tryResume 返回false
DispatchedTask<T>.dispatch
↓ // mode == MODE_UNDISPATCHED
DispatchedTask<T>.resume
↓
DispatchedContinuation # resumeUndispatchedWith
↓
Continuation # resumeWith
如果本例中使用的是Dispatcher.IO,则context.delay获取的是DefaultDelay

看到EventLoopImplBase中的实现
public override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
val timeNanos = delayToNanos(timeMillis)
if (timeNanos < MAX_DELAY_NS) {
val now = nanoTime()
DelayedResumeTask(now + timeNanos, continuation).also { task ->
continuation.disposeOnCancellation(task)
schedule(now, task)
}
}
}
这里使用了DelayedResumeTaqsk进行了延迟执行resumeWith,可以看到续体还是一路被携带的,大家可以自行分析下调用过程。
五、协程种类及关系

Job提供协程的基本操作:start、cancel、join,且声明一个子Job序列:children
a job is a cancellable thing with a life-cycle that culminates in its completion.
JobSupport实现Job的基本方法,实现父子关系、状态控制(父Job取消,子Job全部取消;子Job异常,父子Job全部取消,除SupervisorCoroutine)
A concrete implementation of [Job]. It is optionally a child to a parent job.
AbstractCoroutine,在JobSupport基础上增加协程上下文、resumeWith方法、生命周期回调方法
Abstract base class for implementation of coroutines in coroutine builders.
4、各类协程实现
| 方法名 | 实现 | 作用 | 关键操作、原理 |
|---|---|---|---|
| CoroutineScope.launch | StandaloneCoroutine | 发起非阻塞协程,返回控制器Job | 任务调度(线程池+Handler) |
| CoroutineScope.runBlocking | BlockingCoroutine | 发起阻塞协程 | LockSupport.parkNanos 挂起当前线程 |
| CoroutineScope.async | DeferredCoroutine | 发起非阻塞协程,返回Deferred | CancellableContinuationImpl.getResult方法返回 COROUTINE_SUSPENDED 挂起父协程 |
| coroutineScope | ScopeCoroutine | 继承外部context,返回新的控制器Job | 新建一个新的协程 |
| supervisorScope | SupervisorCoroutine | 一个子协程异常不会影响到其他子协程 | 新建一个协程,重写 childCancelled 方法 |
| withContext | ScopeCoroutineDispatchedCoroutineUndispatchedCoroutine | 在当前协程基础上使用新的context发去协程 | 对比当前协程context与新传入的context,判断使用那种协程,...... |
| withTimeout | TimeoutCoroutine | 超时自动cancel协程 | 各种Dispatcher都有自己的实现 |
| CoroutineScope | ContextScope | 返回一个只实现了context的scope |
- Job状态控制
JobSupport中的注释有详细解释 // TODO
六、协程上下文

上图紫色部分是实现了操作符的相关类
CoroutineContext是可以相加的,加完变成这种结构:

Dispatcher、Job这些都实现了Element接口,都可以在上下文中进行相加操作,换句话说,只要实现Element接口就能加入CoroutineContext,同时需要声明CoroutineContext.Key作为存取的Key
CoroutineContext核心方法就是plus、get,实现上下文的拼接,同时使用伴生对象实现去重
// 举个例子
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
↓
SupervisorJob().plus(Dispatchers.Main)
↓
Job.plus(ContinuationInterceptor)
↓
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) {
// fast path -- avoid lambda creation
// 如果要合并的是一个空上下文,直接返回当前的上下文
this
} else {
// this -> Job, context -> ContinuationInterceptor
context.fold(this) { acc, element ->
// acc -> Job, context -> ContinuationInterceptor
// 取出右侧的上下文的 key, acc.minusKey计算出左侧上下文除去这个key后剩下的上下文内容
val removed = acc.minusKey(element.key)
// removed -> Job
if (removed === EmptyCoroutineContext) {
// acc 与 element 相等
element
} else {
// make sure interceptor is always last in the context (and thus is fast to get when present)
val interceptor = removed[ContinuationInterceptor]
if (interceptor == null) {
// 这个例子最终走向了这个分支
CombinedContext(removed, element)
} else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) {
CombinedContext(element, interceptor)
} else {
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
}
}
恕我直言,这篇文章写的真好:Kotlin协程上下文CoroutineContext是如何可相加的,是时候锻炼下逻辑了 (:з」∠) 我就不展开讲了。
以上,如有错漏敬请告知

