kotlin 协程挂起、恢复原理

587 阅读7分钟

挂起函数

挂起函数:suspend函数,只能在挂起函数或者协程体中被调用,挂起函数并不一定会挂起。

  1. 编译器会自动将 suspend 函数末尾增加一个 Continuation 类型参数。
  2. 函数转换后返回值类型变为 Object,返回值增加含义:标志该挂起函数有没有被挂起。为了适配所有的可能性,转换后的函数返回值类型就只能是 Object 了。

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 函数反编译后如下:

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 中。

上面代码反编译后如下:

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,看下这个调用链。

  1. 创建 StandalonCoroutine,此类继承 AbstractCoroutine,AbstractCoroutine 也实现了 Continuation 接口。

CoroutineScope.launch -> AbstractCoroutine(StandaloneCoroutine/LazyStandaloneCoroutine).start() -> CoroutineStart.invoke().

//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()。

  1. 继续分析 ContinuationImpl.intercepted(),主要调度器(CoroutineDispatcher)的创建。

DispatchedContinuation 是拦截之后的协程体类对象,代理了协程体类continuation对象,并持有调度器 this。上下文中存储的拦截器是在launch() 调用的时候就设置好的,如果我们不进行指定的话,内部会设置一个默认的拦截器 Disptchers.Default。

//以下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)
  1. CoroutineDispatcher 派发任务。 startCoroutineCancellable的最后一个链式调用。下面看下 Dispatchers.IO 的实现。

在 DispatchedContinuation.resumeCancellableWith()中将DispatchedContinuation分发到CoroutineScheduler线程池中 ,最终在Worker的run()方法中触发了DispatchedContinuation的run()。

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() 方法。

//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 类中

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类型的对象:

  1. SuspendLambda 协程体类对象,封装协程体的操作;

  2. DispatchedContinuation 持有线程调度器,代理协程体类对象;

  3. AbstractCoroutine 恢复外部协程挂起resmueWith();

挂起过程

挂起使协程体的操作被return而停止,等待恢复,它阻塞的是协程体的操作,并未阻塞线程。上述启动的流程中就有挂起的操作。下面以withContext()挂起函数来看下协程的挂起与恢复。

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。

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()

override fun resumeWith(result: Result<T>) {  
     val state = makeCompletingOnce(result.toState())  
     // 子协程未完成,父协程需要等待子协程完成之后才可以完成  
     if (state === COMPLETING_WAITING_CHILDREN) return  
     // 子协程全部执行完成或者没有子协程的情况下不需要等待  
     afterResume(state)  
}  
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状态值,执行下一个代码片。

学习资料

源码分析-Kotlin中协程的挂起和恢复

硬核万字解读——Kotlin协程原理解析