协程原理初探——挂起&恢复

496 阅读2分钟

前提基础知识

  • 继承关系:协程体封装类 -> SuspendLambda -> ContinuationImpl -> BaseContinuationImpl -> Continuation
  • Kotlin提供Function0到Function22的接口,即lambda函数最多支持22个参数
  • 协程内lambda代码块,编译器会为其创建一个对象,继承SuspendLambda类,实现 FunctionX接口
  • 编译器会给suspend挂起函数额外在末尾加上Continuation类型参数,Continuation代表协程体本身,内部是状态机相关逻辑,suspend函数执行完毕会调用 continuation.resumeWith() -> invokeSuspend(result) ,回到状态机

示例代码

//协程启动
private fun coroutineTest() {
    CoroutineScope(Dispatchers.Main).launch {
        val a = getRemoteData()
    }
}

//挂起函数
private suspend fun  getRemoteData(): Boolean = withContext(Dispatchers.IO){
    delay(5000)
    return@withContext true
}

将kotlin代码转成java:

Tools -> Kotlin -> Show Kotlin ByteCode -> Decompile

从字节码中也可以看出lambda代码块被编译成 SuspendLambda 的子类:
final class com/stew/kotlinjetpack/MainActivity$coroutineTest$1 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function2

final class com/stew/kotlinjetpack/MainActivity$getRemoteData$2 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function2

协程启动部分java代码逻辑:

pubLic final Cbfeet invoke Suspend (ehotNull Object Sresult) 〈.png

挂起函数部分java代码逻辑:

orlwtefI oJect aetResateouta(Cent Srustsen Beeeolat Ien).png

源码初探

1.启动协程

查看CoroutineScope.launch源码流程

#CoroutineScope.launch
#AbstractCoroutine.start
#CoroutineStart.invoke
#startCoroutineCancellable
#createCoroutineUnintercepted(…).intercepted().resumeCancellableWith(…)//其中createCoroutineUnintercepted(…)返回Continuation
#DispatchedContinuation.resumeCancellableWith
#dispatcher.dispatch(context, this)//其中this是个runnable,因为DispatchedContinuation继承DispatchedTask
#DispatchedTask.run
#continuation.resume(getSuccessfulResult(state))
#resumeWith(Result.success(value))
#Continuation.resumeWith(result: Result)//因为BaseContinuationImpl继承了Continuation
#BaseContinuationImpl.resumeWith
#invokeSuspend(result)

协程启动完毕,这里开始执行协程体内的代码

BaseContinuationImpl.resumeWith内部逻辑比较关键,invokeSuspend方法如果返回COROUTINE_SUSPENDED就会return,即协程被挂起

TThIe Bao Lecemtation TIaL ThIE Tast 1e waed te teratt reteetsth recersten..png

2.协程挂起

这里执行第一个挂起函数getRemoteData

var10000 = var4.getRemoteData(this);
if (var10000 == COROUTINE_SUSPENDED) {
   return var3;
}
break;
  • 如果返回COROUTINE_SUSPENDED,则return,即协程被挂起
  • 如果没有返回COROUTINE_SUSPENDED,则break,继续执行协程体的代码
withContext源码跟踪:

#withContext
#suspendCoroutineUninterceptedOrReturn
#block.startCoroutineCancellable(…)//可以看出,这里和之前协程启动逻辑一样,最终都会执行到续体Continuation的invokeSuspend方法

Pasted Graphic.png 所以这里执行到我们的挂起函数内的协程体的invokeSuspend方法

orlwtefI oJect aetResateouta(Cent Srustsen Beeeolat Ien).png

跟踪 DelayKt.delay(5000L, this):

#delay
#suspendCancellableCoroutine
#suspendCoroutineUninterceptedOrReturn
#cancellable.getResult() -> return COROUTINE_SUSPENDED

Pasted Graphic 1.png

可以看出,delay函数最终返回了COROUTINE_SUSPENDED

3.协程恢复

从DelayKt.delay(5000L, this)可以看出,传入了this,即当前续体

delay执行完毕后,当前续体会通过resume来恢复协程,继续执行invokeSuspend方法剩下的逻辑,即返回true,getRemoteData方法接收了一个Continuation类型的$completion参数,即上一层的续体,getRemoteData返回true结束之后,该上一层的续体会执行resume恢复上一层的invokeSuspend方法

var10000 = var4.getRemoteData(this);
if (var10000 == COROUTINE_SUSPENDED) {
   return var3;
}
break;

即var10000=true,不等于COROUTINE_SUSPENDED,所以执行break,继续执行剩余逻辑


顺便给练习项目求个Star🌟🌟🌟,万分感谢🙏🙏🙏:
github.com/stewForAni/…

WechatIMG382.jpeg

参考文章: