Continuation-Passing Style,续体传递风格
CPS是一种函数式编程风格。在普通函数的参数中增加一个Continuation参数。
例如定义一个普通函数并在main函数中打印:
fun normalFunc(a: Int, b: Int): Int {
return a + b
}
fun main() {
println(normalFunc(1, 2))
}
如果把这个例子改成CPS函数就是:
interface CPSContinuation {
fun plus(result: Int)
}
fun cpsFunc(a: Int, b: Int, continuation: CPSContinuation) {
continuation.plus(a + b)
}
fun main() {
cpsFunc(1, 2, object : CPSContinuation {
override fun plus(result: Int) {
println(result)
}
})
}
续体传递风格的编程简单来说就是,在常规函数中新增一个参数continuation,这个continuation的性质类似于一个lambda对象,从代码上看:
- CPS函数相较于传统函数,多了一个continuation参数。
- continuation内部定义了一个方法(名字随意,不是自动生成),这个方法将传统方法的返回值作为参数,方法内部实现,需要在CPS函数传的continuation对象中实现。
- continuation对象的内部方法将调用常规方法的逻辑做了实现。
如果换成Java风格,无需使用匿名对象,直接用block即可。即函数的结果变成了通过block来传递,协程就是通过在CPS的Continuation回调里结合状态机流转,来实现协程挂起-恢复的功能的。
在协程中,编译器会将挂起函数转换为CPS函数:增加一个continuation参数,并改变return类型为 Any?
。
Suspend 挂起
协程构造器接收一个suspend关键字修饰的闭包,同样的,suspend修饰的方法或lambda表达式,都只能在suspend的代码块范围内使用,准确的来说,suspend修饰的作用范围,和普通函数是相互区分,不相容的。在代码中,一般suspend关键字能够使用的情景有两种,挂起函数和挂起lambda表达式:
suspend () -> Unit
suspend fun function()
这里举例说明suspend的作用,例如以下代码:
suspend fun dummy() {}
suspend fun main() {
val lambda: suspend () -> Unit = {
println(1)
dummy()
println(2)
dummy()
println(3)
}
lambda()
}
这是一个没有挂起操作的协程,它会以此打印1、2。我们在dumy中添加挂起操作
var c: Continuation<Unit>? = null
suspend fun dummy() = suspendCoroutine<Unit> { continuation ->
println("Suspended")
c = continuation
}
重新运行,打印结果为:
1
Suspended
此时main函数一致被挂起,导致阻塞。如果我们需要不阻塞main函数的场景,需要将其改造成普通函数:
// 首先定义一个普通函数到挂起函数的桥接方法
fun builder(c: suspend () -> Unit) {
c.startCoroutine(object: Continuation<Unit> {
override val context = EmptyCoroutineContext
override fun resumeWith(result: Result<Unit>) {
result.getOrThrow()
}
})
}
然后将main函数改造成普通函数:
fun main() {
val lambda: suspend () -> Unit = {
println(1)
dummy()
println(2)
dummy()
println(3)
}
builder {
lambda()
}
}
打印结果为:
1
Suspended
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
可以看出,挂起后,main函数执行结束了。那么,如果我们要恢复这个挂起函数呢?
通过c?.resume(Unit)
即可实现,需要注意的是,当挂起函数如果不处于挂起状态调用这个方法,会抛出IllegalStateException: Already resumed
。
从这个例子可以得出结论,挂起函数和普通函数之间,需要创建一个顶级桥接协程,协程在普通函数中运行,而挂起函数在协程作用域范围内运行。
Suspend Lambda
如上面例子中的lambda:
val lambda: suspend () -> Unit = {
println(1)
dummy()
println(2)
dummy()
println(3)
}
反编译为字节码,存在以下内容:
// 挂起lambda
final class com/chunyu/coroutines/CreateTestKt$main$lambda$1 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function1
编译器在编译时会为挂起lambda生成一个内部类,这个东西仅在字节码中能够看见,反编译为Java就消失了。
它的继承关系是 继承自SuspendLambda
和Function1
。这两个东西,是由SuspendFuncation
生成的。
// 为了区分挂起函数类型和普通函数类型,所有挂起函数类型都应该实现这个接口
internal interface SuspendFunction
// suspend lambda 继承自此类
internal abstract class SuspendLambda(
public override val arity: Int,
completion: Continuation<Any?>?
) : ContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction
挂起lambda有一个指定的挂起函数类型: SuspendFuncation { N }
,N是lambda表达式的参数。它只在编译期间存在,并转换为Function{N+1}
和SuspendLambda
。因为 SuspendFuncation { N }
在运行时不存在,它的作用是用来区分普通函数类型和挂起函数类型。
它可以用于 is SuspendFuncation { N }
和as SuspendFuncation { N }
表达式。例如:
boolean var2 = lambda instanceof SuspendFunction ? TypeIntrinsics.isFunctionOfArity(lambda, 1) : false;
System.out.print(var2);
SuspendLambda类中的arity,就是参数数量 + 1。
kotlin.suspend
public inline fun <R> suspend(noinline block: suspend () -> R): suspend () -> R = block
通常在普通的函数或lambda中,不能直接使用suspend关键字,且lambda没有参数的情况下,可以使用这个方法创建一个可挂起的block。
Suspend函数
将上面的lambda改造成函数:
suspend fun lambdaFunc() {
println(1)
dummy()
println(2)
dummy()
println(3)
}
字节码中,也存在一个内部类:
final class com/chunyu/coroutines/SuspendTestKt$lambdaFunc1$1 extends kotlin/coroutines/jvm/internal/ContinuationImpl
与挂起lambda不同的是他们的继承关系。虽然两者的继承关系不同,但是继承关系中都有ContinuationImpl
。
这个类与CPS有关。在CPS中,每个挂起函数都会被编译器增加一个continuation参数,并且更改返回类型为 Any?。而无论是挂起lambda还是挂起函数,都需要增加一个continuation参数,这个参数的类型,自然需要通过编译器自动生成。就是字节码中提到的内部类。这个内部类中,包含了协程中最重要的状态变化逻辑,称之为状态机。
状态机
编译器通过使用状态机将顺序代码变成可挂起的。挂起调用将代码分为不同的状态。挂起调用和状态之间是一对一的关系:每次挂起调用会获取一个状态,每个状态只有一次挂起调用。
状态以挂起调用为结束。编译器会将挂起调用之前的所有代码存放到当前状态中,最后一次调用之后的代码,也会存放到一个单独的状态中。例如:
dummy() // 假设这里有挂起操作
println(1)
dummy()
println(2)
编译器按照以下方式拆分代码:
==========
dummy()
----------
println(1)
dummy()
----------
println(2)
==========
函数的边界以 === 表示,状态的边界以 --- 表示。编译器在分割这个函数后会生成以下代码:
val $result: Any? = null
when (this.label) {
0 -> {
this.label = 1
$result = dummy(this)
if ($result == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
goto 1
}
1 -> {
println(1)
this.label = 2
$result = dummy(this)
if ($result == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
goto 2
}
2 -> {
println(2)
return Unit
}
else -> {
throw IllegalStateException("call to 'resume' before 'invoke' with coroutine")
}
}
然后,编译器会将这个状态机代码放在 invokeSuspend
函数中。在挂起函数和挂起方法的章节我们提到过,挂起lambda继承自SuspendLamda,而挂起函数会为状态机生成一个ContinuationImpl的子类,它们都继承自BaseContinuationImpl:
abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
public final override fun resumeWith(result: Result<Any?>)
protected abstract fun invokeSuspend(result: Result<Any?>): Any?
protected open fun releaseIntercepted()
public open fun create(completion: Continuation<*>): Continuation<Unit>
public open fun create(value: Any?, completion: Continuation<*>): Continuation<Unit>
public override fun toString(): String
public override val callerFrame: CoroutineStackFrame?
public override fun getStackTraceElement(): StackTraceElement?
}
这里的 invokeSuspend
函数就是存放状态机代码的地方。
举个例子,例如以下代码:
suspend fun dummy() {}
suspend fun main() {
val lambda: suspend () -> Unit = {
dummy()
println(1)
dummy()
println(2)
dummy()
println(3)
}
lambda()
}
反编译为Java,一步一步分析,首先是编译器生成了两个类SuspendTestKt$$$main
和SuspendTestKt
,前者是main函数生成的类,后者是其他方法所在的类。
final class SuspendTestKt$$$main extends Lambda implements Function1 {
private final String[] mArgs;
SuspendTestKt$$$main(String[] args) {
super(1);
this.mArgs = args;
}
public final Object invoke(Object obj) {
return SuspendTestKt.main((Continuation)obj);
}
}
main函数调用时,指向了SuspendTestKt的main方法:
public final class SuspendTestKt {
@Nullable
public static final Object main(@NotNull Continuation $completion) {
Function1 lambda = (Function1)(new Function1((Continuation)null) {
// ...
});
Object result = lambda.invoke($completion);
return result == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? result : Unit.INSTANCE;
}
}
可以看出,调用了lambda对象的invoke方法获取一个result,再根据这个result返回COROUTINE_SUSPENDED或Unit。
这个lambda对象是:
Function1 lambda = (Function1)(new Function1((Continuation)null) {
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
byte text;
@BlockTag1: {
Object result;
@BlockTag2: {
result = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
this.label = 1;
if (SuspendTestKt.dummy(this) == result) {
return result;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
case 2:
ResultKt.throwOnFailure($result);
break @BlockTag2;
case 3:
ResultKt.throwOnFailure($result);
break @BlockTag1;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
text = 1;
System.out.println(text);
this.label = 2;
if (SuspendTestKt.dummy(this) == result) {
return result;
}
}
text = 2;
System.out.println(text);
this.label = 3;
if (SuspendTestKt.dummy(this) == result) {
return result;
}
}
text = 3;
System.out.println(text);
return Unit.INSTANCE;
}
@NotNull
public final Continuation create(@NotNull Continuation completion) {
Intrinsics.checkNotNullParameter(completion, "completion");
Function1 funcation = new <anonymous constructor>(completion);
return funcation;
}
public final Object invoke(Object object) {
return ((<undefinedtype>)this.create((Continuation)object)).invokeSuspend(Unit.INSTANCE);
}
});
这个对象有invokeSuspend方法、create方法、invoke方法和label字段,很明显这个和BaseContinuationImpl的定义基本一致。这个就是挂起lambda经过编译器处理后生成的ContinuationImpl的实现。
逐个方法分析,首先是invoke:
public final Object invoke(Object object) {
return ((<undefinedtype>)this.create((Continuation)object)).invokeSuspend(Unit.INSTANCE);
}
invoke依次调用create和invokeSuspend,create代码:
@NotNull
public final Continuation create(@NotNull Continuation completion) {
Intrinsics.checkNotNullParameter(completion, "completion");
Function1 funcation = new <anonymous constructor>(completion);
return funcation;
}
生成一个匿名Function1对象并返回,最后是invokeSuspend:
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
byte text;
@BlockTag1: {
Object result;
@BlockTag2: {
result = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
this.label = 1;
if (SuspendTestKt.dummy(this) == result) {
return result;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
case 2:
ResultKt.throwOnFailure($result);
break @BlockTag2;
case 3:
ResultKt.throwOnFailure($result);
break @BlockTag1;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
text = 1;
System.out.println(text);
this.label = 2;
if (SuspendTestKt.dummy(this) == result) {
return result;
}
}
text = 2;
System.out.println(text);
this.label = 3;
if (SuspendTestKt.dummy(this) == result) {
return result;
}
}
text = 3;
System.out.println(text);
return Unit.INSTANCE;
}
这里先省略代码块BlockTag1中的细节:
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
byte text;
@BlockTag1: {
// ..
}
text = 3;
System.out.println(text);
return Unit.INSTANCE;
}
先执行代码块BlockTag1,BlockTag1执行完成后,text = 3,然后打印,并返回 Unit。对比我们的kotlin代码:
suspend fun main() {
val lambda: suspend () -> Unit = {
// =============
// state 1
dummy()
// -------------
// state 2
println(1)
dummy()
// -------------
// state 3
println(2)
dummy()
// -------------
// state 4
println(3)
// =============
}
lambda()
}
通过 === 和 --- 将lambda分为四个状态,BlockTag1后续的代码,等同于状态4。
再看看BlockTag1的伪代码:
@BlockTag1: {
Object result;
@BlockTag2 {
// ...
}
text = 2;
System.out.println(text);
this.label = 3;
if (SuspendTestKt.dummy(this) == result) {
return result;
}
}
这里text = 2,同时label置为了3,label的作用后续会讲到,BlockTag1中,BlockTag2的后续代码可以理解为状态3。
继续分析BlockTag2:
@BlockTag2: {
result = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
this.label = 1;
if (SuspendTestKt.dummy(this) == result) {
return result;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
case 2:
ResultKt.throwOnFailure($result);
break @BlockTag2;
case 3:
ResultKt.throwOnFailure($result);
break @BlockTag1;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
text = 1;
System.out.println(text);
this.label = 2;
if (SuspendTestKt.dummy(this) == result) {
return result;
}
}
BlockTag2中,switch语句后续部分的text = 1,label赋值2,对应状态2。这个时候不难发现,如果dumy函数没有挂起操作,代码按顺序执行,不会走到if (SuspendTestKt.dummy(this) == result)
中,这个时候会继续执行代码块外部的代码,也就是说this.label = 2;
及其后执行顺序的伪代码:
this.label = 2;
text = 2;
System.out.println(text);
this.label = 3;
text = 3;
System.out.println(text);
return Unit.INSTANCE;
label初始化时默认为0,所以会先走switch语句中的case 0分支:
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
this.label = 1;
if (SuspendTestKt.dummy(this) == result) {
return result;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
case 2:
ResultKt.throwOnFailure($result);
break @BlockTag2;
case 3:
ResultKt.throwOnFailure($result);
break @BlockTag1;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
Case 0分支什么也没有,对应了dumy的空实现,所以是状态1。
另外,这个switch语句按顺序会在BlockTag1最先执行,目的是直接根据label分发状态。
那么,如果dumy内存在挂起操作呢?把dumy函数内增加一个delay操作:
suspend fun dummy() {
delay(10000)
}
对应反编译结果:
@Nullable
public static final Object dummy(@NotNull Continuation $completion) {
Object result = DelayKt.delay(10000L, $completion);
return result == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? result : Unit.INSTANCE;
}
如果存在挂起操作,函数会返回COROUTINE_SUSPENDED,在最深处的代码块中存在:
result = IntrinsicsKt.getCOROUTINE_SUSPENDED();
if (SuspendTestKt.dummy(this) == result) {
return result;
}
导致整个invokeSuspend函数直接return,这就是挂起导致的协程阻塞。
协程启动流程
以launch为例分析:
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) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
newCoroutineContext
方法中,拿到了外部协程上下文然后再拼接自身的协程上下文,从而实现了继承上下文的能力。然后创建了一个协程对象。
这里的
LazyStandaloneCoroutine
继承自StandaloneCoroutine
。
他们都继承自AbstractCoroutine,继承关系如下:
从这张继承关系图可以看出AbstractCoroutine实现了JobSupport
类和Job
、Continuation<T>
以及CoroutineScope
三个接口。
- Job接口返回的是协程任务,用来做返回值类型处理。
- CoroutineScope是协程作用域接口,定义了能够获取协程上下文的能力。
- JobSupport是Job的具体实现。
那么Continuation<T>
是个什么东西?
Continuation
public interface Continuation<in T> {
/**
* 与此Continuation相关的协程上下文
*/
public val context: CoroutineContext
/**
* 恢复执行相应的协程,传递一个成功或失败的[result]作为最后一个暂停点的返回值
*/
public fun resumeWith(result: Result<T>)
}
这个interface就是Kotlin协程使用的CPS中的continuation的定义。
协程的创建
继续分析launch方法中的代码,以StandaloneCoroutine对象为例,它最后调用到了coroutine.start(start, coroutine, block)
:
// coroutine.start(start, coroutine, block)
/*
* 通过给定的代码块和启动策略来启动协程。此函数最多应在此协程上调用一次。
*/
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
startAbstractCoroutine(start, receiver, this, block)
}
这里的receiver类型为StandaloneCoroutine;通过备注可以看出,因CoroutineStart的值不同,会走不同的实现方法。先查看startAbstractCoroutine的内部实现:
internal actual inline fun <T, R> startAbstractCoroutine(
start: CoroutineStart,
receiver: R,
coroutine: AbstractCoroutine<T>,
noinline block: suspend R.() -> T
) {
startCoroutineImpl(start, receiver, coroutine, null, block)
}
跟进到startCoroutineImpl方法内部:
internal fun <T, R> startCoroutineImpl(
start: CoroutineStart,
receiver: R,
completion: Continuation<T>,
onCancellation: ((cause: Throwable) -> Unit)?,
block: suspend R.() -> T
) = when (start) {
CoroutineStart.DEFAULT -> block.startCoroutineCancellable(receiver, completion, onCancellation)
CoroutineStart.ATOMIC -> block.startCoroutine(receiver, completion)
CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
CoroutineStart.LAZY -> Unit // will start lazily
}
根据CoroutineStart的不同类型做了不同的逻辑:
- startCoroutineCancellable
- startCoroutine
- startCoroutineUndispatched
- LAZY return Unit
依次查看三个方法的实现:
startCoroutine
public fun <R, T> (suspend R.() -> T).startCoroutine(
receiver: R,
completion: Continuation<T>
) {
createCoroutineUnintercepted(receiver, completion).intercepted().resume(Unit)
}
startCoroutineCancellable
// 使用这个函数以可取消的方式启动协程,这样就可以在等待调度时取消。
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
receiver: R, completion: Continuation<T>,
onCancellation: ((cause: Throwable) -> Unit)? = null
) =
runSafely(completion) {
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)
}
startCoroutineUndispatched
internal fun <R, T> (suspend (R) -> T).startCoroutineUndispatched(receiver: R, completion: Continuation<T>) {
startDirect(completion) { actualCompletion ->
withCoroutineContext(completion.context, null) {
startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
}
}
}
除了startCoroutineUndispatched
方法,其他两个函数的内部逻辑基本一致:再调用该方法返回对象的intercepted()
方法拦截协程,最后通过不同的resume方法来启动协程。启动协程分三个步骤:
- 通过
createCoroutineUnintercepted
方法创建Continuation - 调用
intercepted()
方法 - 调用不同的
resume
唤起协程
创建Continuation
createCoroutineUnintercepted
该函数在不同的平台有不同的实现,而且有两种泛型参数的实现,后者的区别是接收类型和返回类型,都是用来创建一个未被拦截的协程。
public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
completion: Continuation<T>
): Continuation<Unit> {
val probeCompletion = probeCoroutineCreated(completion)
return if (this is BaseContinuationImpl)
create(probeCompletion)
else
createCoroutineFromSuspendFunction(probeCompletion) {
(this as Function1<Continuation<T>, Any?>).invoke(it)
}
}
js平台的实现:
public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
completion: Continuation<T>
): Continuation<Unit> =
createCoroutineFromSuspendFunction(completion) {
val a = this.asDynamic()
if (jsTypeOf(a) == "function") a(completion)
else this.invokeSuspendSuperType(completion)
}
内部会根据Continuation的泛型类型来判断执行不同的实现方法,create
或createCoroutineFromSuspendFunction
。先看看这里的BaseContinuationImpl
的继承关系:
+------------+
|Continuation|
+------+-----+
^
|
+---------+----------+
|BaseContinuationImpl+<---------------+
+---------+----------+ |
^ |
| |
+-------+--------+ +-------------+------------+
|ContinuationImpl| |RestrictedContinuationImpl|
+-------+--------+ +-------------+------------+
^ ^
| |
+-----+-------+ +-----------+-----------+
|SuspendLambda| |RestrictedSuspendLambda|
+-------------+ +-----------------------+
可以从这里看出,Continuation接口是顶级接口,本质上,它也是协程机制的核心,协程机制使用CPS,每个挂起函数和lambda都接收一个额外的Continuation参数。 每个编译器生成的continuation都继承自BaseContinuationImpl:
abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
public final override fun resumeWith(result: Result<Any?>)
protected abstract fun invokeSuspend(result: Result<Any?>): Any?
protected open fun releaseIntercepted()
public open fun create(completion: Continuation<*>): Continuation<Unit>
public open fun create(value: Any?, completion: Continuation<*>): Continuation<Unit>
public override fun toString(): String
public override val callerFrame: CoroutineStackFrame?
public override fun getStackTraceElement(): StackTraceElement?
}
这里的resumeWith
是final的,但是BaseContinuationImpl
提供了invokeSuspend
函数,resumeWith
做了以下事情:
- 用户调用
resumeWith
,它内部会调用ivnokeSuspend
。通过result恢复挂起的协程。 - 当协程执行结束,会调用completion返回给外部调用者。
- 将捕获的异常封装在result中返回给调用者。
编译器会自动生成一个参数或没有参数的create方法,供createCoroutineUnintercepted
调用。
ContinuationImpl
增加了intercepted
和intercepted()
函数,供后续调用。
internal abstract class ContinuationImpl(
completion: Continuation<Any?>?,
private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
constructor(completion: Continuation<Any?>?) : this(completion, completion?.context)
public override val context: CoroutineContext
get() = _context!!
@Transient
private var intercepted: Continuation<Any?>? = null
public fun intercepted(): Continuation<Any?> =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
protected override fun releaseIntercepted() {
val intercepted = intercepted
if (intercepted != null && intercepted !== this) {
context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted)
}
this.intercepted = CompletedContinuation // just in case
}
}
一般情况下,因为函数限定了泛型为(suspend () -> T)
的拓展函数,所以这里的this一般都是BaseContinuationImpl
的子类型。
这里的create函数是一个CPS函数,它的作用是将value保存在continuation中,并将continuation提供给外部使用。
public final Continuation create(@Nullable Object value, @NotNull Continuation completion)
另一种逻辑是,当this不是BaseContinuationImpl
类型时,会走createCoroutineFromSuspendFunction方法:
private inline fun <T> createCoroutineFromSuspendFunction(
completion: Continuation<T>,
crossinline block: (Continuation<T>) -> Any?
): Continuation<Unit> {
val context = completion.context
// 当协程尚未启动时label == 0时或label == 1时
return if (context === EmptyCoroutineContext)
object : RestrictedContinuationImpl(completion as Continuation<Any?>) {
// ....
}
else
object : ContinuationImpl(completion as Continuation<Any?>, context) {
private var label = 0
override fun invokeSuspend(result: Result<Any?>): Any? =
when (label) {
0 -> {
label = 1
result.getOrThrow() // 如果尝试以异常开始,则重新抛出异常(将被 BaseContinuationImpl.resumeWith 捕获
block(this) // 运行block, 可能return或者挂起
}
1 -> {
label = 2
result.getOrThrow() // block已挂起的结果
}
else -> error("This coroutine had already completed")
}
}
}
首先取出协程上下文对象,如果写成上下文和EmptyCoroutineContext地址是否相同,若相同,返回一个RestrictedContinuationImpl
匿名对象。否则,返回一个ContinuationImpl
匿名对象,其中的逻辑基本相同。
- label默认为0
- label == 0时,将label置为1,result检查是否抛出异常,然后运行协程中的block。这里是开始恢复执行代码。
- label == 1时,将label置为2,result检查是否抛出异常。这里是协程执行完成。
- label为其他值,报错,当前协程已运行。
RestrictedContinuationImpl和ContinuationImpl的区别是,RestrictedContinuationImpl要求协程上下文为EmptyCoroutineContext,而且缺少拦截器intercepted
的属性和方法:
internal abstract class RestrictedContinuationImpl(
completion: Continuation<Any?>?
) : BaseContinuationImpl(completion) {
init {
completion?.let {
require(it.context === EmptyCoroutineContext) {
"Coroutines with restricted suspension must have EmptyCoroutineContext"
}
}
}
public override val context: CoroutineContext
get() = EmptyCoroutineContext
}
无论哪种方式,最后都会拿到一个Continuation对象,然后进行下一步调用:intercepted()
intercepted
看到这个方法你会怀疑,为什么Continuation中没有定义这个方法还能调用?实际上它是一个拓展方法,查看它的实现:
// 定义实现
public expect fun <T> Continuation<T>.intercepted(): Continuation<T>
// native平台实际实现
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
(this as? ContinuationImpl)?.intercepted() ?: this
从平台实现发现,如果当前对象是ContinuationImpl的子类,才能调用到。刚刚提到过RestrictedContinuationImpl的情况,就是没有intercepted方法,直接返回自身。
ContinuationImpl.intercepted()
方法中,从context取出ContinuationInterceptor
,然后调用其interceptContinuation
方法,再将这个拦截器对象保存起来。
public interface ContinuationInterceptor : CoroutineContext.Element {
public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
// ...
}
ContinuationInterceptor有一个我们比较熟悉的子类CoroutineDispatcher
,协程调度器的基类:
public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
public override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
// ...
}
实际上,调度器切换线程的操作也是在这里完成的,DispatchedContinuation
对象内部:
internal class DispatchedContinuation<in T>(
@JvmField val dispatcher: CoroutineDispatcher,
@JvmField val continuation: Continuation<T>
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {
// ...
override fun resumeWith(result: Result<T>) {
val context = continuation.context
val state = result.toState()
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_ATOMIC
dispatcher.dispatch(context, this)
} else {
executeUnconfined(state, MODE_ATOMIC) {
withCoroutineContext(this.context, countOrElement) {
continuation.resumeWith(result)
}
}
}
}
inline fun resumeUndispatchedWith(result: Result<T>) {
withContinuationContext(continuation, countOrElement) {
continuation.resumeWith(result)
}
}
inline fun resumeCancellableWith(
result: Result<T>,
noinline onCancellation: ((cause: Throwable) -> Unit)?
) {
val state = result.toState(onCancellation)
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_CANCELLABLE
dispatcher.dispatch(context, this)
} else {
executeUnconfined(state, MODE_CANCELLABLE) {
if (!resumeCancelled(state)) {
resumeUndispatchedWith(result)
}
}
}
}
}
列举的三个方法很明显和下一步操作resume有关。
resume
回顾startCoroutineImpl方法:
CoroutineStart.DEFAULT -> block.startCoroutineCancellable(receiver, completion, onCancellation)
CoroutineStart.ATOMIC -> block.startCoroutine(receiver, completion)
CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
CoroutineStart.LAZY -> Unit // will start lazily
我们逐个分析startCoroutineCancellable、startCoroutine和startCoroutineUndispatched:
startCoroutineCancellable
最终调用resumeCancellableWith
方法:
public fun <T> Continuation<T>.resumeCancellableWith(
result: Result<T>,
onCancellation: ((cause: Throwable) -> Unit)? = null
): Unit = when (this) {
is DispatchedContinuation -> resumeCancellableWith(result, onCancellation)
else -> resumeWith(result)
}
当拦截方法intercepted返回DispatchedContinuation时,就会走到刚刚提到过的DispatchedContinuation中的resumeCancellableWith方法:
inline fun resumeCancellableWith(
result: Result<T>,
noinline onCancellation: ((cause: Throwable) -> Unit)?
) {
val state = result.toState(onCancellation)
if (dispatcher.isDispatchNeeded(context)) {
// ...
dispatcher.dispatch(context, this)
} else {
executeUnconfined(state, MODE_CANCELLABLE) {
if (!resumeCancelled(state)) {
resumeUndispatchedWith(result)
}
}
}
}
dispatcher.isDispatchNeeded(context)
方法是用来判断是否应该使用dispatch方法来执行协程,若应该返回true,大多数调度器默认返回true;若返回false,则协程应该立即在当前线程中恢复。
获取状态机的状态,如果dispatcher.isDispatchNeeded(context) == true
,则通过调度器的dispatch方法来处理。否则调用executeUnconfined方法。
若不是DispatchedContinuation类型,则直接调用Continuation的resumeWith方法。Continuation的resumeWith方法后续会单独分析。
startCoroutine
当CoroutineStart类型为ATOMIC时,会走这个方法:
public fun <T> (suspend () -> T).startCoroutine(
completion: Continuation<T>
) {
createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}
该方法最后一步调用的是resume:
@InlineOnly
public inline fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
resume也是Continuation的拓展方法,内部调用是Continuation的resumeWith方法。
startCoroutineUndispatched
internal fun <R, T> (suspend (R) -> T).startCoroutineUndispatched(receiver: R, completion: Continuation<T>) {
startDirect(completion) { actualCompletion ->
withCoroutineContext(completion.context, null) {
startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
}
}
}
这个方法比较复杂,首先是最外面的startDirect,用于debug追踪处理。然后是withCoroutineContext,这个简单了,用于切换上下文;最后是startCoroutineUninterceptedOrReturn
:
public actual inline fun <T> (suspend () -> T).startCoroutineUninterceptedOrReturn(
completion: Continuation<T>
): Any? = (this as Function1<Continuation<T>, Any?>).invoke(completion)
public actual inline fun <R, T> (suspend R.() -> T).startCoroutineUninterceptedOrReturn(
receiver: R,
completion: Continuation<T>
): Any? = (this as Function2<R, Continuation<T>, Any?>).invoke(receiver, completion)
这个方法的含义是,将传入的suspend闭包,强转为cps风格的函数,并调用自动生成的Contionuation的invoke方法。
Continuation的resumeWith
Continuation的实现需要从编译生成的代码和普通代码两条路径分析。
- 在launch的调用流程中,我们创建了一个
StandaloneCoroutine
对象,它的父类AbstractCoroutine
实现了Continuation<T>
。 - 在编译器中,会自动将挂起lambda生成为
BaseContinuationImpl
的实现类,里面定义了resumeWith的实现逻辑。
代码中的继承
AbstractCoroutine
的resumeWith:
public abstract class AbstractCoroutine<in T>(
@JvmField internal val parentContext: CoroutineContext,
initParentJob: Boolean,
active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
public final override fun resumeWith(result: Result<T>) {
val state = makeCompletingOnce(result.toState())
if (state === COMPLETING_WAITING_CHILDREN) return
afterResume(state)
}
protected open fun afterResume(state: Any?): Unit = afterCompletion(state)
protected open fun afterCompletion(state: Any?) {}
// ...
}
state的生成逻辑:
internal fun makeCompletingOnce(proposedUpdate: Any?): Any? {
loopOnState { state ->
val finalState = tryMakeCompleting(state, proposedUpdate)
when {
finalState === COMPLETING_ALREADY ->
throw IllegalStateException(
"Job $this is already complete or completing, " +
"but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull
)
finalState === COMPLETING_RETRY -> return@loopOnState
else -> return finalState // COMPLETING_WAITING_CHILDREN or final state
}
}
}
它的作用是完成这个Job,当Job已完成时调用会抛出IllegalStateException。 state获取完,如果实在等待子项完成,则直接return,否则调用afterResume,afterResume直接调用了afterCompletion。 afterCompletion在不同的子类中实现:
- BlockingCoroutine
override fun afterCompletion(state: Any?) {
// 唤醒阻塞线程
if (Thread.currentThread() != blockedThread)
unpark(blockedThread)
}
- ScopeCoroutine
override fun afterCompletion(state: Any?) {
// Resume in a cancellable way by default when resuming from another context
uCont.shareableInterceptedResumeCancellableWith(recoverResult(state, uCont))
}
- DispatchedCoroutine
override fun afterCompletion(state: Any?) {
// Call afterResume from afterCompletion and not vice-versa, because stack-size is more
// important for afterResume implementation
afterResume(state)
}
override fun afterResume(state: Any?) {
if (tryResume()) return // completed before getResult invocation -- bail out
// Resume in a cancellable way because we have to switch back to the original dispatcher
uCont.shareableInterceptedResumeCancellableWith(recoverResult(state, uCont))
}
除了BlockingCoroutine,其他两者最终都是uCont.shareableInterceptedResumeCancellableWith(recoverResult(state, uCont))
:
internal actual inline fun <T> Continuation<T>.shareableInterceptedResumeCancellableWith(result: Result<T>) {
intercepted().resumeCancellableWith(result)
}
最终的重任又来到了resumeCancellableWith,要么DispatchedContinuation,交给调度器处理,要么直接调用Continuation自己的resumeWith。
编译器对挂起lambda的处理
在编译层面的实现在BaseContinuationImpl
中:
internal abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
// ...
public final override fun resumeWith(result: Result<Any?>) {
// 此循环在 current.resumeWith(param) 中展开递归,以使恢复时的堆栈跟踪更清晰、更短
var current = this
var param = result
while (true) {
// 这个方法用于debug追踪,在每个恢复的Continuation上调用“resume”调试探测器,以便调试库基础结构可以精确跟踪暂停的调用堆栈的哪一部分已经恢复
probeCoroutineResumed(current)
with(current) {
val completion = completion!! // 尝试在未完成的情况下恢复Continuation时快速失败
val outcome: Result<Any?> = try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted() // 状态机对象正在终止
if (completion is BaseContinuationImpl) {
// 通过循环展开递归
current = completion
param = outcome
} else {
// 顶级completion调用——invoke and return
completion.resumeWith(outcome)
return
}
}
}
}
}
这个方法的大致流程可以分为:
- 获取执行结果
大部分current,也就是当前continuation对象不为空时,尝试调用到
invokeSuspend(param)
,如果invokeSuspend(param)
返回了COROUTINE_SUSPENDED,则直接return,否则最终结果为成功Result.success(outcome)
,若在执行过程中捕获到异常,最终结果为失败Result.failure(exception)
。 - 释放拦截器
- 递归子项
- 使用最终结果outcome调用顶级completion的resumeWith(outcome)。