挂起函数
挂起函数:suspend函数,只能在挂起函数或者协程体中被调用,挂起函数并不一定会挂起。
- 编译器会自动将 suspend 函数末尾增加一个 Continuation 类型参数。
- 函数转换后返回值类型变为 Object,返回值增加含义:标志该挂起函数有没有被挂起。为了适配所有的可能性,转换后的函数返回值类型就只能是 Object 了。
kotlin
fun main(args: Array<String>) {
MainScope().launch {
val token = getToken()
val data = requestData(token)
Log.d("TAG", "main: $data")
}
}
suspend fun getToken(): String {
delay(1000)
return "token"
}
suspend fun requestData(token: String) = withContext(Dispatchers.IO) {
"value for $token"
}
上面声明的三个 suspend 函数反编译后如下:
less
Object getToken(@NotNull Continuation var0)
Object requestData(final String token, Continuation $completion)
状态流转
协程调用的本质,可以说就是 状态机 + continuation 的流转
- 每个协程体( 被suspend修饰的lambda 表达式 )会生成成一个 SuspendLambda 类。
- 利用状态机控制各个函数的调用顺序,协程挂起就是对应return结束后续的执行,协程恢复只是跳转到下一种状态中。
- getToken(continuation),requestData(token, continuation),delay(1000L, continuation) 共用同一个 continuation 实例,所以 label 是递增的。
- 切换协程之前,状态机会把之前的结果以成员变量的方式保存在 continuation 中。
上面代码反编译后如下:
typescript
public static final void main(String[] paramArrayOfString) {
BuildersKt.launch$default(CoroutineScopeKt.MainScope(), null, null, new TestKt$main$1(null), 3, null);
}
// 每个协程体会生成一个 SuspendLambda
class TestKt$main$1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Unit>, Object> {
TestKt$main$1(Continuation<? super TestKt$main$1> param1Continuation) {
super(2, param1Continuation);
}
public final Continuation<Unit> create(Object param1Object, Continuation<?> param1Continuation) {
return (Continuation<Unit>) new TestKt$main$1((Continuation) param1Continuation);
}
public final Object invoke(CoroutineScope param1CoroutineScope, Continuation<? super Unit> param1Continuation) {
return ((TestKt$main$1) create(param1CoroutineScope, param1Continuation)).invokeSuspend(Unit.INSTANCE);
}
int label; // 表示当前执行的流程, 能够流转也就是说在协程调用过程这个实例一直被传递下去
// result 是上次标志(label)执行的结果
public final Object invokeSuspend(Object $result) {
Object var10000;
label17: {
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();// 挂起的常量值
switch (this.label) {
case 0: // 状态0 调用 getToken
this.label = 1; // label 置为 1,准备进入下一状态
var10000 = getToken(this);
if (var10000 == var4) { // 如果是挂起直接结束执行即挂起
return var4; //getToken 会挂起所以会走到这里
}
// 如果 getToken 不是挂起就往下走到调用 requestData 的地方了
break;
case 1: // 当 label 变为1时,这里的结果是 getToken 返回的,用于下面 requestData
var10000 = $result;
break;
case 2:
var10000 = $result; // label 1 的结果即 requestData 的返回
break label17;
}
String token = (String)var10000; // label 0的返回值
this.label = 2; // label 置为 2, 准备进入下一状态
var10000 = requestData(token, this);
if (var10000 == var4) { // 如果是挂起函数,则结束
return var4;
}
}
String data = (String)var10000; // requestData 的返回值,然后打印
return Boxing.boxInt(Log.d("TAG", "main: " + data));
}
}
static final class TestKt$getToken$1 extends ContinuationImpl {
... 同TestKt$main$1 ...
}
static final class TestKt$requestData$2 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super String>, Object> {
... 同TestKt$main$1 ...
}
public static final Object requestData(String paramString, Continuation<? super String> paramContinuation) {
return BuildersKt.withContext((CoroutineContext)Dispatchers.getIO(), new TestKt$requestData$2(paramString, null), paramContinuation);
}
public static final Object getToken(Continuation<? super String> paramContinuation) {
......
}
SuspendLambda 继承结构如下图,注意其中标红的方法。
怎么把挂起、恢复过程串联起来哪?接下来看挂起、恢复过程。
协程创建过程
常用的启动协程是通过 launch,看下这个调用链。
- 创建 StandalonCoroutine,此类继承 AbstractCoroutine,AbstractCoroutine 也实现了 Continuation 接口。
CoroutineScope.launch -> AbstractCoroutine(StandaloneCoroutine/LazyStandaloneCoroutine).start() -> CoroutineStart.invoke().
kotlin
//block->协程块
//receiver/completion->AbstractCoroutine(实现了Continuation)
operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
when (this) {
DEFAULT -> block.startCoroutineCancellable(receiver, completion)
ATOMIC -> block.startCoroutine(receiver, completion)
UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
LAZY -> Unit // will start lazily
}
//block 的扩展函数,链式调用。createCoroutineUnintercepted:SuspendLambdar子类的创建
fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>): Unit = runSafely(completion) {
createCoroutineUnintercepted(completion).intercepted().resumeCancellableWith(Result.success(Unit))
}
//SuspendLambdar子类的创建,也是block的扩展函数,不好找。位于IntrinsicsJvm.kt。
//receiver/completion->StandaloneCoroutine(AbstractCoroutine)
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(receiver: R, completion: Continuation<T>): Continuation<Unit> {
val probeCompletion = probeCoroutineCreated(completion)//completion 直接返回了
//this 是 SuspendLambdar: TestKt$main$1.create()
return if (this is BaseContinuationImpl) create(receiver, probeCompletion)//probeCompletion->AbstractCoroutine
else {
createCoroutineFromSuspendFunction(probeCompletion) {
(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
}
}
}
这个过程创建了StandalonCoroutine,此类继承 AbstractCoroutine(也实现了 Continuation),AbstractCoroutine 类主要负责协程的恢复和结果的返回:
create()方法创建了一个协程体类SuspendLambda的实例,创建完成后接下来会走 intercepted()。
- 继续分析 ContinuationImpl.intercepted(),主要调度器(CoroutineDispatcher)的创建。
DispatchedContinuation 是拦截之后的协程体类对象,代理了协程体类continuation对象,并持有调度器 this。上下文中存储的拦截器是在launch() 调用的时候就设置好的,如果我们不进行指定的话,内部会设置一个默认的拦截器 Disptchers.Default。
kotlin
//以下this是协程体类的实例:SuspendLambda, 不是AbstractCoroutine
fun <T> Continuation<T>.intercepted(): Continuation<T> = (this as? ContinuationImpl)?.intercepted() ?: this
//ContinuationImpl
fun intercepted(): Continuation<Any?> = intercepted ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this).also { intercepted = it }
//CoroutineDispatcher(eg. Dispatchers.IO)
//DispatchedContinuation代理了协程体类continuation对象,并持有调度器 this
fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation)
- CoroutineDispatcher 派发任务。 startCoroutineCancellable的最后一个链式调用。下面看下 Dispatchers.IO 的实现。
在 DispatchedContinuation.resumeCancellableWith()中将DispatchedContinuation分发到CoroutineScheduler线程池中 ,最终在Worker的run()方法中触发了DispatchedContinuation的run()。
kotlin
object DefaultScheduler : ExperimentalCoroutineDispatcher() {
//IO 的实现是 LimitingDispatcher
val IO: CoroutineDispatcher = LimitingDispatcher(this, ...)
}
//DispatchedContinuation(实现了DispatchedTask->Runnable)
//task里会调用 Continuation.resumeWith(result)
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)//DispatchedContinuation
} else { // Dispatcher.Unconfined 才会走这
executeUnconfined(state, MODE_CANCELLABLE) {
if (!resumeCancelled(state)) {
resumeUndispatchedWith(result)
}
}
}
}
//交给线程池处理, CoroutineScheduler代码省略
fun dispatchWithContext(block: Runnable, context: TaskContext, tailDispatch: Boolean) {
try {
//线程池 CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
coroutineScheduler.dispatch(block, context, tailDispatch)
} catch (e: RejectedExecutionException) {
DefaultExecutor.enqueue(coroutineScheduler.createTask(block, context))
}
}
回归到DispatchedContinuation 它实现了Runnable() 接口, run()方法的实现在 DispatchedContinuation 父类 DispatchedTask 中,看下线程池执行的任务 run() 方法。
kotlin
//DispatchedTask
public final override fun run() {
try {
val delegate = delegate as DispatchedContinuation<T>//代理了协程体类 Continuation
val continuation = delegate.continuation //取出协程体类SusbendLambda
withContinuationContext(continuation, delegate.countOrElement) {
val context = continuation.context //上下文
...
// 调用协程体的resumeWith
continuation.resume(getSuccessfulResult(state))
}
}
}
continuation.resume() 内部调用了 resumeWith()方法 , 因为此处的 continuation是协程体类,所以 其实现是在 BaseContinuationImpl 类中
kotlin
abstract class BaseContinuationImpl(//completion:实参是一个AbstractCoroutine
val completion: Continuation<Any?>?) : Continuation<Any?> {
override fun resumeWith(result: Result<Any?>) {
var current = this
var param = result
while (true) {
with(current) {
val completion = completion!!
val outcome: Result<Any?> =
try {
// 调用invokeSuspend方法,协程体真正开始执行
val outcome = invokeSuspend(param)
// invokeSuspend方法返回值为COROUTINE_SUSPENDED,resumeWith方法被return,结束执行,说明执行了挂起操作
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) {
current = completion
param = outcome
} else {
//在示例代码中,completion是一个AbstractCoroutine,是指launch函数创建的StandaloneCoroutine
completion.resumeWith(outcome) //协程结束回调,调用 resumeWith 并 return
return
}
}
}
}
...
}
内部有一个无限循环,假设我们协程体内没有挂起函数,那么将会循环执行 invokeSuspend()方法直到结束,方法内部通过状态机依次执行。那么当遇到挂起函数的时候,也就是方法返回 COROUTINE_SUSPENDED 挂起标识,将直接 return 退出循环,同时协程体代码也会退出使协程挂起,因为退出的是协程体,并不会造成线程阻塞。
在上文的分析中出现了三个Continuation类型的对象:
-
SuspendLambda 协程体类对象,封装协程体的操作;
-
DispatchedContinuation 持有线程调度器,代理协程体类对象;
-
AbstractCoroutine 恢复外部协程挂起resmueWith();
挂起过程
挂起使协程体的操作被return而停止,等待恢复,它阻塞的是协程体的操作,并未阻塞线程。上述启动的流程中就有挂起的操作。下面以withContext()挂起函数来看下协程的挂起与恢复。
kotlin
suspend fun <T> withContext(context: CoroutineContext,block: suspend CoroutineScope.() -> T): T {
// 返回启动withContext的协程体
return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
//uCont->Continuation,发起withContext的协程对象
val oldContext = uCont.context
val newContext = oldContext + context
newContext.ensureActive()
//oldContext/newContext dispatcher一致时会直接调用block并return. 省略
......
//调度器不一致时,使用新的dispatcher(DispatchedCoroutine)
//DispatchedCoroutine也是一个AbstractCoroutine对象,负责协程完成的回调
//将 uCont 传入,DispatchedCoroutine这样就持有了需要恢复的协程
val coroutine = DispatchedCoroutine(newContext, uCont)
//withContext()的协程体的启动和之前的启动流程是一样的
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()//返回结果为挂起还是完成
}
}
suspendCoroutineUninterceptedOrReturn 看不到具体的实现,这里 uCont 就是传入的原协程体类对象。
withContext()的协程体的启动(startCoroutineCancellable)和之前启动流程是一样的,DispatchedCoroutin是AbstractCoroutine的一个子类,并且在创建DispatchedCoroutin时的传参是外层协程体对象,这样当withContext()的协程体完成的时候就能恢复外层协程体的运行。
是否挂起取决于coroutine.getResult()。 接下来看DispatchedCoroutine。
kotlin
class DispatchedCoroutine {
/************** 挂起相关 *****************/
fun getResult(): Any? {
//返回COROUTINE_SUSPENDED就是真正的挂起
if (trySuspend()) return COROUTINE_SUSPENDED
return state as T//直接返回函数执行结果
}
private val _decision = atomic(UNDECIDED)
private fun trySuspend(): Boolean {
//_decision默认为UNDECIDED,所以,trySuspend返回true
_decision.loop { decision ->
when (decision) {
//compareAndSet原子操作,当前值与预期值一致时返回true,以原子方式更新自身的值
UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, SUSPENDED)) return true
RESUMED -> return false
else -> error("Already suspended")
}
}
}
}
恢复过程
withContex()的协程的启动调用了block.startCoroutineCancellable(coroutine, coroutine),这个方法之前在【协程启动流程】看过实现了,startCoroutineCancellable的第二个参数为(DispatchedCoroutine)协程完成的回调。DispatchedCoroutine,而DispatchedCoroutine里面持有待恢复的协程(uCont),看一下它的类图:
从类图中可以看出DispatchedCoroutine是AbstractCoroutine的一个子类,AbstractCoroutine是协程完成时的回调,会调用它的内部方法resumeWith(),内部的处理逻辑最后会触发JubSpuuort#afterCompletion(),而在DispatchedCoroutine中重写了afterCompletion()
kotlin
override fun resumeWith(result: Result<T>) {
val state = makeCompletingOnce(result.toState())
// 子协程未完成,父协程需要等待子协程完成之后才可以完成
if (state === COMPLETING_WAITING_CHILDREN) return
// 子协程全部执行完成或者没有子协程的情况下不需要等待
afterResume(state)
}
kotlin
class DispatchedCoroutine {
uCont: Continuation<T> // 外部需要恢复的协程
/************** 下面的函数都是跟恢复相关的了 *****************/
private fun tryResume(): Boolean {
_decision.loop { decision ->
// 在getResult()时如果挂起了值为SUSPENDED,未挂起为UNDECIDED
when (decision) {
// 未发生挂起
UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, RESUMED)) return true
// 发生挂起
SUSPENDED -> return false
else -> error("Already resumed")
}
}
}
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
//恢复时通过外部上下文(uCont)来进行,实现在协程启动时分析过
uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
}
}
afterResume()中首先判断了协程是否被挂起,如已挂起则恢复外部的协程。恢复外部协程时,同样是通过线程调度,将协程在指定的线程运行,resumeCancellableWith就可以在挂起恢复时,重新切回线程,再次触发invokeSuspend(),根据label状态值,执行下一个代码片。