前言

本篇解析Kotlin/JVM中的协程的实现原理。

初看suspend关键字

下面的例子模拟一个网络请求:

class Temp {suspend fun fetchData(argument: String): Boolean {val result = netRequest(argument)return result == 0}// 模拟网络请求suspend fun netRequest(argument: String): Int {delay(1000)return argument.length}
}

这两个方法都使用了suspend关键字修饰,我们将这个文件的字节码反编译为等同效果的Java代码:

public final class Temp {@Nullablepublic final Object fetchData(@NotNull String argument, @NotNull Continuation var2) {Object $continuation;label25: {if (var2 instanceof <undefinedtype>) {$continuation = (<undefinedtype>)var2;if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;break label25;}}$continuation = new ContinuationImpl(var2) {// $FF: synthetic fieldObject result;int label;@Nullablepublic final Object invokeSuspend(@NotNull Object $result) {this.result = $result;this.label |= Integer.MIN_VALUE;return Temp.this.fetchData((String)null, this);}};}Object $result = ((<undefinedtype>)$continuation).result;Object var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();Object var10000;switch(((<undefinedtype>)$continuation).label) {case 0:ResultKt.throwOnFailure($result);((<undefinedtype>)$continuation).label = 1;var10000 = this.netRequest(argument, (Continuation)$continuation);if (var10000 == var6) {return var6;}break;case 1:ResultKt.throwOnFailure($result);var10000 = $result;break;default:throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");}int result = ((Number)var10000).intValue();return Boxing.boxBoolean(result == 0);}@Nullablepublic final Object netRequest(@NotNull String argument, @NotNull Continuation var2) {Object $continuation;label20: {if (var2 instanceof <undefinedtype>) {$continuation = (<undefinedtype>)var2;if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;break label20;}}$continuation = new ContinuationImpl(var2) {// $FF: synthetic fieldObject result;int label;Object L$0;@Nullablepublic final Object invokeSuspend(@NotNull Object $result) {this.result = $result;this.label |= Integer.MIN_VALUE;return Temp.this.netRequest((String)null, this);}};}Object $result = ((<undefinedtype>)$continuation).result;Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();switch(((<undefinedtype>)$continuation).label) {case 0:ResultKt.throwOnFailure($result);((<undefinedtype>)$continuation).L$0 = argument;((<undefinedtype>)$continuation).label = 1;if (DelayKt.delay(1000L, (Continuation)$continuation) == var5) {return var5;}break;case 1:argument = (String)((<undefinedtype>)$continuation).L$0;ResultKt.throwOnFailure($result);break;default:throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");}return Boxing.boxInt(argument.length());}
}

几行协程相关的代码,竟然对应了这么多的Java代码,可见kotlin编译器为我们做了很多事情

上面代码的可读性不高,例如有<undefinedtype>这种未定义的类型,我使用jd-guiTemp.class文件再进行了一次反编译,获取到了更多信息,我将上面的反编译的一大串代码和jd-gui反编译获取的信息进行整合,并且对一些类和变量进行适当的重命名,得出信息更完整且可读性更高的「Temp.class反编译后对应的Java代码」,首先是fetchData相关的:

public final Object fetchData(@NotNull String argument,@NotNull Continuation completion) {Object $continuation;label25:{if (completion instanceof FetchDataStateMachine) {$continuation = (FetchDataStateMachine) completion;if (($continuation.label & Integer.MIN_VALUE) != 0) {$continuation.label -= Integer.MIN_VALUE;break label25;}}$continuation = new FetchDataStateMachine(completion);}Object $result = $continuation.result;Object resultTemp;switch ($continuation.label) {case 0:ResultKt.throwOnFailure($result);$continuation.label = 1;resultTemp = this.netRequest(argument, (Continuation) $continuation);if (resultTemp == COROUTINE_SUSPENDED) {return COROUTINE_SUSPENDED;}break;case 1:ResultKt.throwOnFailure($result);resultTemp = $result;break;default:throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");}int result = ((Number) resultTemp).intValue();return Boxing.boxBoolean(result == 0);
}static final class FetchDataStateMachine extends ContinuationImpl {Object result;int label;FetchDataStateMachine(Continuation $completion) {super($completion);}@Nullablepublic final Object invokeSuspend(@NotNull Object $result) {this.result = $result;this.label |= Integer.MIN_VALUE;return Temp.this.fetchData(null, (Continuation<? super Boolean>) this);}
}

netRequest相关的代码,与fetchData相关的代码,在结构和形式上类似:

public final Object netRequest(@NotNull String argument,@NotNull Continuation completion) {Object $continuation;label20:{if (completion instanceof NetRequestStateMachine) {$continuation = (NetRequestStateMachine) completion;if (($continuation.label & Integer.MIN_VALUE) != 0) {$continuation.label -= Integer.MIN_VALUE;break label20;}}$continuation = new NetRequestStateMachine(completion);}Object $result = $continuation.result;switch ($continuation.label) {case 0:ResultKt.throwOnFailure($result);$continuation.functionParameter = argument;$continuation.label = 1;if (DelayKt.delay(1000L, (Continuation) $continuation) == COROUTINE_SUSPENDED) {return COROUTINE_SUSPENDED;}break;case 1:argument = (String) ($continuation.functionParameter);ResultKt.throwOnFailure($result);break;default:throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");}return Boxing.boxInt(argument.length());
}static final class NetRequestStateMachine extends ContinuationImpl {Object result;int label;Object functionParameter;NetRequestStateMachine(Continuation $completion) {super($completion);}@Nullablepublic final Object invokeSuspend(@NotNull Object $result) {this.result = $result;this.label |= Integer.MIN_VALUE;return Temp.this.netRequest(null, (Continuation<? super Integer>) this);}
}

可以发现,反编译后的Java代码中,fetchDatanetRequest方法都多了一个Continuation completion参数,这是Kotlin Compiler帮我们做的,对于suspend修饰的函数,编译的时候Kotlin Compiler会帮我们在该函数中传入一个Continuation参数,使用Continuation参数代替了suspend修饰符,这个参数有什么含义呢?

初识续体

续体是理解协程工作原理的一个关键。

先看传统的网络请求:

data class User(val id: Long, val name: String)interface Callback {fun success(user: User)fun failure(t: Throwable)
}class Model {fun getUserInfo(callback: Callback) {Thread.sleep(1000) // 模拟网络请求callback.success(User(1, "giagor"))}
}class Business {val model = Model()fun getUserInfo() {model.getUserInfo(object : Callback {override fun success(user: User) {showMsg(user.toString())}override fun failure(t: Throwable) {showMsg(t.message ?: "")}})}fun showMsg(msg: String) {// ...}
}

在使用Model进行网络请求的时候,使用Callback接收网络请求的结果,我们这时候可以将Callback看作一个续体,即网络请求的续体,用于接收网络请求的结果。

在协程中使用Continuation接口表示一个续体,它代表一个挂起点之后的延续,即 挂起点之后的剩余应执行的代码

public interface Continuation<in T> {// 与该续体对应的协程的上下文public val context: CoroutineContext// 恢复对应协程的执行,并且传递一个表示成功或失败的result作为最后一个挂起点的返回值public fun resumeWith(result: Result<T>)
}

Kotlin 1.3,也有可以方便地调用resumeWith的扩展函数:

public inline fun <T> Continuation<T>.resume(value: T): Unit =resumeWith(Result.success(value))public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =resumeWith(Result.failure(exception))

正如前面所说,对于suspend修饰的函数,Kotlin Compiler会帮我们在该函数中传入一个Continuation参数,使用Continuation参数代替了suspend修饰符,通过Continuation参数,Kotlin Compiler可以将我们的协程代码转化为等价的回调代码,也就是说,Kt编译器帮我们写好了那些回调的代码,至于怎么帮我们写的后面会分析,这种通过传递Continuation来控制异步调用流程被称作CPS变换(Continuation-Passing-Style Transformation

状态机

fetchData函数编译时会生成下面的一个静态内部类(续体):

static final class FetchDataStateMachine extends ContinuationImpl {Object result;int label;FetchDataStateMachine(Continuation $completion) {super($completion);}@Nullablepublic final Object invokeSuspend(@NotNull Object $result) {this.result = $result;this.label |= Integer.MIN_VALUE;return Temp.this.fetchData(null, (Continuation<? super Boolean>) this);}
}

FetchDataStateMachine的继承关系如下:

FetchDataStateMachine -> ContinuationImpl -> BaseContinuationImpl -> Continuation

FetchDataStateMachine接收一个名称为$completionContinuation参数,$completion被保存在父类BaseContinuationImpl中:

internal abstract class BaseContinuationImpl(public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {...}

通过$completion可以将fetchData函数的执行结果传递回给调用fetchData的函数,有了$completion,才有能力去实现回调

状态机FetchDataStateMachine声明了resultlabel两个变量

  • result表示上一个Continuation的结果,比如有函数AB,函数内部分别声明了ContinuationAContinuationBA调用B并且将ContinuationA传入B中保存。在后续回调的过程中,ContinuationA可以从result变量中拿到ContinuationB::invokeSuspend的执行结果
  • labelKotlin Compiler可以识别函数内部哪个地方会挂起,每一个挂起点(suspension point)被表示为状态机的一个状态(state),这些状态通过switch case语句表示出来。label表示当前应该执行状态机的哪一个状态,具体来说就是要进入哪一个case,通过label变量就记录下了状态机当前的状态

再看下fetchData的前半部分代码:

public final Object fetchData(@NotNull String argument,@NotNull Continuation completion) {Object $continuation;label25:{if (completion instanceof FetchDataStateMachine) {$continuation = (FetchDataStateMachine) completion;if (($continuation.label & Integer.MIN_VALUE) != 0) {$continuation.label -= Integer.MIN_VALUE;break label25;}}$continuation = new FetchDataStateMachine(completion);}...
}

它会判断传入的completion是否为FetchDataStateMachine类型,若是则对它的label变量做些操作,若不是则直接创建一个FetchDataStateMachine并且传入completioncompletion会被保存下来)。

再看下fetchData的后半部分代码:

public final Object fetchData(@NotNull String argument,@NotNull Continuation completion) {Object $continuation;...Object $result = $continuation.result;Object resultTemp;switch ($continuation.label) {case 0:ResultKt.throwOnFailure($result);$continuation.label = 1;resultTemp = this.netRequest(argument, (Continuation) $continuation);if (resultTemp == COROUTINE_SUSPENDED) {return COROUTINE_SUSPENDED;}break;case 1:ResultKt.throwOnFailure($result);resultTemp = $result;break;default:throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");}int result = ((Number) resultTemp).intValue();return Boxing.boxBoolean(result == 0);
}

fetchData方法原先的代码语句会被划分为switch下的多个case语句,在这里就是

FetchDataStateMachine中的label变量就是控制当前要执行哪个case分支。

可见,函数与续体构成了一个有限状态机(FSM,即 Finite-State Machine),来控制协程代码的执行

何为「非阻塞式挂起」?

netRequest方法中,调用了delay(1000)挂起了当前的协程,简单看下delay方法反编译后的代码:

public static final Object delay(long timeMillis, @NotNull Continuation $completion) {if (timeMillis <= 0L) {return Unit.INSTANCE;} else {// 实现类CancellableContinuationImpl cancellableContinuationImpl = new CancellableContinuationImpl(IntrinsicsKt.intercepted($completion), 1);cancellableContinuationImpl.initCancellability();// 向上转型CancellableContinuation cont = (CancellableContinuation)cancellableContinuationImpl;if (timeMillis < Long.MAX_VALUE) {// 延时操作getDelay(cont.getContext()).scheduleResumeAfterDelay(timeMillis, cont);}// 获取执行结果Object result = cancellableContinuationImpl.getResult();if (result == COROUTINE_SUSPENDED) {DebugProbesKt.probeCoroutineSuspended($completion);}// 返回结果return result;}
}

在该方法里会执行延时操作,如果需要挂起,就会返回COROUTINE_SUSPENDED值给调用者。

结合fetchDatanetRequestdelay反编译的代码,我们可以得出下面的这个调用图:

图中红色的线表示函数返回COROUTINE_SUSPENDED,需要挂起。当delay方法需要挂起的时候,它返回COROUTINE_SUSPENDED,接着netRequest方法返回COROUTINE_SUSPENDED,接着fetchData方法返回COROUTINE_SUSPENDED,重复这个过程直到调用栈的最上层。

通过这种「结束方法调用」的方式,让协程暂时不在这个线程上面执行,让线程可以去处理其它的任务(包括执行其它的协程),这也就是为什么协程的挂起不会阻塞当前的线程,这也是「非阻塞式挂起」的由来

如何恢复?

既然协程挂起了,那就有相应的协程的恢复。先说结论:协程恢复的实质是对续体进行回调

暂时还没有研究delay函数的具体实现,但是delay函数会在某个子线程执行等待操作,等延时时间到达之后,就会调用传给delay函数的$completionresumeWith方法,也就是调用NetRequestStateMachineresumeWith方法NetRequestStateMachine的继承关系、父类如下:

NetRequestStateMachine -> ContinuationImpl -> BaseContinuationImpl -> Continuation

BaseContinuationImpl目前是我们分析的一个重点,它主要做了下面的几件事情:

  1. 保存completion:它保存了fetchData方法的FetchDataStateMachine实例,使得可以一级一级地向上回调续体。
  2. 重写resumeWith方法:BaseContinuationImpl重写了Continuation接口的resumeWith方法,该方法用于恢复协程,也是协程恢复的核心逻辑。

我们查看BaseContinuationImpl类的定义:

internal abstract class BaseContinuationImpl(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 resumevar current = thisvar param = resultwhile (true) {// 在每个恢复的continuation进行调试探测,使得调试库可以精确跟踪挂起的调用栈中哪些部分// 已经恢复了。probeCoroutineResumed(current)with(current) {val completion = completion!! // fail fast when trying to resume continuation without completionval outcome: Result<Any?> =try {val outcome = invokeSuspend(param)if (outcome === COROUTINE_SUSPENDED) returnResult.success(outcome)} catch (exception: Throwable) {Result.failure(exception)}releaseIntercepted() // this state machine instance is terminatingif (completion is BaseContinuationImpl) {// unrolling recursion via loopcurrent = completionparam = outcome} else {// top-level completion reached -- invoke and returncompletion.resumeWith(outcome)return}}}}protected abstract fun invokeSuspend(result: Result<Any?>): Any?protected open fun releaseIntercepted() {// does nothing here, overridden in ContinuationImpl}    ...
}

重点是resumeWith方法的实现,它在一个while(true)循环下面执行回调的逻辑。我们结合前面给出的fetchDatanetRequest反编译后的代码,看看delay函数的延时时间到达时调用NetRequestStateMachineresumeWith方法,后续的执行流程是怎样的:

  1. 执行NetRequestStateMachine父类BaseContinuationImplresumeWith方法。
  2. 执行当前续体也就是NetRequestStateMachineinvokeSuspend方法(NetRequestStateMachine有实现该方法,忘记了的话可以回头看看之前的反编译代码)。
  3. NetRequestStateMachineinvokeSuspend方法调用了netRequest方法,并且将续体自身作为参数传入。
  4. netRequest方法中,由于completion的类型就是NetRequestStateMachine因此可以直接使用该续体,不用像之前第一次进入netRequest方法那样需要创建一个新的续体。此时续体的label值为1,于是进入netRequestcase 1语句分支。

实际上这个过程有对续体的label进行一些运算转化的操作,但是最终label的值都是1,做的运算转化操作不影响我们的分析,因此并不是重点

  1. 从续体中取出一开始传入netRequest方法的参数,也就是argument,返回argument.length。为了方便后面阐述,这里将该返回值argument.length记为netRequest-Return
  2. 接着netRequest方法结束,NetRequestStateMachine::invokeSuspend方法也执行结束,netRequest-Return也作为invokeSuspend方法的返回值,该返回值会传递到BaseContinuationImplresumeWith方法中,在resumeWith方法中,将netRequest-Return包装为Result保存到outcome变量中。
  3. 判断NetRequestStateMachine持有的completion是否为BaseContinuationImpl类型,我们知道它持有的实例其实就是FetchDataStateMachine,因此肯定是BaseContinuationImpl,于是进行了变量的更新
    // 把current更新为FetchDataStateMachine实例current = completion// 把param更新为outcome(包装了netRequest-Return的Result)param = outcome

通过这种方式,其实就可以实现回调,我们继续往后看。

  1. 继续进行下一轮while循环,在with块中会执行FetchDataStateMachine::invokeSuspend,在invokeSuspend里,将传入的参数param保存到result变量里(其实这和传统的回调类似,传统的回调中也是要将下层的执行结果回调给上层),接着调用了fetchData方法。
  2. fetchData方法中,由于传入的completion已是FetchDataStateMachine类型,因此无需再去创建新的续体。由于此时续体label的值为1,所以会进入case 1语句,并且将netRequest方法的执行结果保存在resultTemp变量中,最终fetchData方法结束并返回结果result == 0,为了方便阐述,将fetchData方法的执行结果记为fetchData-Return
  3. FetchDataStateMachine::invokeSuspend方法也会结束并返回fetchData-Return,然后在BaseContinuationImplresumeWith方法中将fetchData-Return包装为Result。然后会判断FetchDataStateMachine持有的completion是否为BaseContinuationImpl类型。
  4. 代码的后续走向,我们目前是不清楚的,我们得知道在协程中调用fetchData方法的时候会做些什么,才能清楚后续的代码走向

从上面的流程分析中,我们对协程的恢复有了一个基本的认识,下面给出流程图进行总结:

再看看上面续体的调用过程,其实就是层层往上地调用续体的invokeSuspend方法,从过程来看有点像递归调用,但是BaseContinuationImpl::resumeWith的实现却和递归不太一样,它的实现是在while(true)循环中,对续体调用一次invokeSuspend方法,然后记录它的返回结果,将这个返回结果作为下一个续体invokeSuspend的方法参数。

简单来讲,就是在调用一个续体的invokeSuspend方法,待这个方法执行结束后,再调用下一个续体的invokeSuspend方法。这样做的一个原因是避免调用栈过深,在BaseContinuationImpl::resumeWith也有相关的注释说明:

This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume

启动协程

我们在一个协程中去调用fetchData方法:

class Temp2 {fun execute() {GlobalScope.launch(Dispatchers.Main) {Temp().fetchData("argument")}}
}

通过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) elseStandaloneCoroutine(newContext, active = true)coroutine.start(start, coroutine, block)return coroutine
}

协程中的代码会被包装为一个block,默认情况下会创建一个StandaloneCoroutine,然后调用它的start方法并返回StandaloneCoroutine

StandaloneCoroutine间接的实现了Job接口和Continuation<T>接口,如下:

private open class StandaloneCoroutine(parentContext: CoroutineContext,active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active) {override fun handleJobException(exception: Throwable): Boolean {handleCoroutineException(context, exception)return true}
}public abstract class AbstractCoroutine<in T>(/*** The context of the parent coroutine.*/@JvmFieldprotected val parentContext: CoroutineContext,active: Boolean = true
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {...}

可以看出StandaloneCoroutine身兼多职,实现了Job, Continuation<T>, CoroutineScope接口。后面代码跟踪可以得出一个结论,最顶层的续体实现是协程自身,也就是协程恢复的时候续体会一层层地往上回调,最顶层的续体就是协程coroutine自身,即StandaloneCoroutine(这里以StandaloneCoroutine为例)

另外还要注意一点,launch方法中传入的block块类型:

 block: suspend CoroutineScope.() -> Unit

它等价于下面的这种函数类型:

// CoroutineScope:扩展函数转化而来
// Continuation:suspend关键字转化而来,Continuation参数由编译器传入
block : (CoroutineScope,Continuation) -> Unit// 或者通过Function2的形式表示
block : Function2<CoroutineScope,Continuation,Unit>

接着跟踪下启动协程的调用过程。在launch方法中,调用了AbstractCoroutine::start方法:

    public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {initParentJob()// 语法糖,实际是调用CoroutineStart.invoke方法start(block, receiver, this)}

CoroutineStart::invoke方法:

    public 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}

launch方法可以知道CoroutineStart的默认值是CoroutineStart.DEFAULT,因此会调用到blockstartCoroutineCancellable方法:

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

我在AS跟踪createCoroutineUnintercepted的代码调用时,发现会跳转到IntrinsicsKt.class文件,这个文件里面找不到方法的源代码,最后找到了IntrinsicsJvm.kt文件,找到createCoroutineUnintercepted方法源码,如下:

# R:CoroutineScope
# T:Unit
@SinceKotlin("1.3")
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(receiver: R,completion: Continuation<T>
): Continuation<Unit> {// probeCoroutineCreated方法直接返回completionval probeCompletion = probeCoroutineCreated(completion)return if (this is BaseContinuationImpl)create(receiver, probeCompletion)else {createCoroutineFromSuspendFunction(probeCompletion) {(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)}}
}

这里会判断this的类型是否为BaseContinuationImplthis就是我们之前在launch中传入的lambda块,那么这个lambda代码块是什么类型的呢?想要知道这个答案,我们得对这一节刚开始给出的代码进行反编译

kotlin代码:

class Temp2 {fun execute() {GlobalScope.launch(Dispatchers.Main) {Temp().fetchData("argument")}}
}

对反编译后的java代码进行适当的重命名和调整,得出:

public final class Temp2 {...static final class LaunchLambda extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Unit>, Object> {int label;LaunchLambda(Continuation $completion) {super(2, $completion);}@Nullablepublic final Object invokeSuspend(@NotNull Object $result) {switch (this.label) {case 0:ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);this.label = 1;if ((new Temp()).fetchData("argument", (Continuation<? super Boolean>) this) == COROUTINE_SUSPENDED)return COROUTINE_SUSPENDED;(new Temp()).fetchData("argument", (Continuation<? super Boolean>) this);return Unit.INSTANCE;case 1:ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);return Unit.INSTANCE;}throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");}@NotNullpublic final Continuation<Unit> create(@Nullable Object value, @NotNull Continuation<? super LaunchLambda> $completion) {return (Continuation<Unit>) new LaunchLambda($completion);}@Nullablepublic final Object invoke(@NotNull CoroutineScope p1, @Nullable Continuation<?> p2) {return ((LaunchLambda) create(p1, p2)).invokeSuspend(Unit.INSTANCE);}}
}

可以看出在Temp2里面会自动生成一个静态内部类LaunchLambda,它对应着launch方法中传入的lambda块。LaunchLambda的继承关系(由上到下,子类到父类的顺序):

LaunchLambda
-> SuspendLambda // 用suspend修饰的lambda块都会继承至这个类
-> ContinuationImpl
-> BaseContinuationImpl // 重写了resumeWith函数
-> Continuation

OK,回到createCoroutineUnintercepted方法中,现在可以回答刚刚提出的问题了,lambda传入的lambda块是不是BaseContinuationImpl类型呢?根据上面的继承关系得出,当然是!那么它就会调用LaunchLambdacreate方法,注意第二个参数传入的是completion(代码中写的是probeCompletion),它最终会被保存在父类BaseContinuationImplcompletion变量中,这个completion参数就是launch方法中创建的StandaloneCoroutine,即协程本身,它作为协程恢复时的最顶层续体

通过调用create方法获取到一个LaunchLambda实例,createCoroutineUnintercepted方法执行结束并返回LaunchLambda实例,接着代码执行又回到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)}

这里有两部分调用,先是调用intercepted方法,然后再调用resumeCancellableWith方法。intercepted方法与续体拦截机制有关,后面会介绍,这里先忽略,这里直接认为调用了LaunchLambda实例的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)
}

那么会走到resumeWith方法,前面提到过该方法在父类BaseContinuationImpl实现,在该方法里面会调用invokeSuspend方法,invokeSuspend方法在LaunchLambda中实现了,如下:

@Nullable
public final Object invokeSuspend(@NotNull Object $result) {switch (this.label) {case 0:ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);this.label = 1;if ((new Temp()).fetchData("argument", (Continuation<? super Boolean>) this) == COROUTINE_SUSPENDED)return COROUTINE_SUSPENDED;(new Temp()).fetchData("argument", (Continuation<? super Boolean>) this);return Unit.INSTANCE;case 1:ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);return Unit.INSTANCE;}throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}

一开始label的值为0,所以会进入case 0语句分支,在该语句分支里面,会设置label的值为1,然后创建一个Temp对象并且调用它的fetchData方法,并把LaunchLambda自身作为参数传入,也就是LaunchLambda实例会被保存在fetchData方法创建的续体的completion变量里,方便协程恢复的时候进行回调。

现在续体的持有图:

到了这里,从启动一个协程到协程最终是如何挂起的,我们已经可以串联起来了。在「如何恢复?」一节中,协程恢复的最后几个步骤我们还没有分析,这里把它分析完,然后整个协程恢复的流程也可以串起来了。

协程恢复的后续流程:

  1. FetchDataStateMachine::invokeSuspend执行完后,会在BaseContinuationImplresumeWith方法中判断FetchDataStateMachine所持有的completion(即LaunchLambda)是否为BaseContinuationImpl类型,由LaunchLambda的继承关系,容易得出答案为「是」,所以会进入下一轮while循环,调用LaunchLambdainvokeSuspend方法。
  2. 由于label = 1所以会进入case 1语句,里面直接return Unit。接着判断LaunchLambda持有的completion(即StandaloneCoroutine)是否为BaseContinuationImpl类型,根据StandaloneCoroutine的继承关系容易得出答案为「不是」,所以会调用StandaloneCoroutineresumeWith方法。
  3. StandaloneCoroutineresumeWith方法在父类AbstractCoroutine中实现:
    public final override fun resumeWith(result: Result<T>) {val state = makeCompletingOnce(result.toState())// 如果在等子协程完成,则返回if (state === COMPLETING_WAITING_CHILDREN) return// 应该是做一些后续处理afterResume(state)}

此时最顶层的续体(协程自身)也恢复了。

  1. BaseContinuationImpl::resumeWith方法执行结束,整个协程的恢复也完成了。

在之前流程图的基础上进行补充完善:

一、协程至上而下调用的流程图(协程挂起)

其中蓝色的文本和线条表示新增的,红色的文本和线条表示挂起的过程。

二、协程至下而上恢复的流程图(协程恢复)

其中蓝色的文本和线条表示新增的,橙色的文本和线条表示方法调用的结束。

协程上下文

协程上下文CoroutineContext定义了协程的行为,它记录了当前协程所持有的信息,是协程运行中一个重要的数据对象。CoroutineContext是一个接口:

public interface CoroutineContext {...}

在续体中就有CoroutineContext的相关信息:

public interface Continuation<in T> {// 与该续体对应的协程的上下文public val context: CoroutineContext// 恢复对应协程的执行,并且传递一个表示成功或失败的result作为最后一个挂起点的返回值public fun resumeWith(result: Result<T>)
}

下面几种元素都是「协程上下文」的元素:

  • Job:控制协程的生命周期。
  • CoroutineDispatcher:将工作分派到适当的线程。
  • CoroutineName:协程的名称,可用于调试。
  • CoroutineExceptionHandler:处理未捕获的异常。

CoroutineContext可以看做是CoroutineContext.Element的一个集合,集合中的每个元素都可以使用CoroutineContext.Key进行定位,且每个元素的Key都是不同的。

CoroutineContext.Element的定义:

    public interface Element : CoroutineContext {...}

可以看到Element本身也实现了CoroutineContext接口,这很奇怪,看上去好像是Int实现了List<Int>接口一样,为什么元素本身也是集合了呢?其实这主要是为了方便API的设计,这样的话,一个元素比如Job也可以直接作为一个CoroutineContext,而不需要创建一个只包含一个元素的List,多个元素之间也可以通过「+」进行拼接,如:

scope.launch(CoroutineName("coroutine") + Dispatchers.Main) {...}

这里的「+」其实是操作符重载,对应CoroutineContext声明的plus方法:

    public operator fun plus(context: CoroutineContext): CoroutineContext = ...

「协程上下文」存储元素的方式比较巧妙,它内部并不是创建一个集合,集合的每个位置都存放一个元素。它借助了一个CombinedContext结构来实现数据的存取,CombinedContext的定义及get方法:

internal class CombinedContext(private val left: CoroutineContext,private val element: Element
) : CoroutineContext, Serializable {override fun <E : Element> get(key: Key<E>): E? {var cur = thiswhile (true) {cur.element[key]?.let { return it }val next = cur.leftif (next is CombinedContext) {cur = next} else {return next[key]}}}...
}

从构造函数中可以看出它包含两部分内容:leftelement。也就是说一个CombinedContext内部可能包含多个元素。

  • left:可能是普通的上下文元素(CoroutineContext.Element),也可能又是一个CombinedContext(又包含多个上下文元素)。
  • element:一个协程上下文元素。

CombinedContextget方法中,有一个while(true)循环,执行过程如下:

  1. 它会先判断当前element元素与传入的key是否相符,是的话直接返回该元素,否则获取到left部分。
  2. leftCombinedContext部分,则对left变量重复步骤1。
  3. left不是CombinedContext部分,则直接调用它的get方法获取元素(获取不到则返回null)。

另外,也可以看出element先于left被访问,所以越靠右边的上下文元素,其优先级越高

Key用于标识协程上下文元素,看看它的定义:

public interface CoroutineContext {...public interface Key<E : Element>public interface Element : CoroutineContext {// 用于标识元素的Keypublic val key: Key<*>...}
}

CoroutineContext.Element有个抽象类实现,可以让我们更方便地实现上下文元素:

public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element

CoroutineName为例,分析如何实现一个协程上下文元素:

public data class CoroutineName(val name: String/* CoroutineName.Key可以简写为CoroutineName */
) : AbstractCoroutineContextElement(CoroutineName) {public companion object Key : CoroutineContext.Key<CoroutineName>...
}

首先声明一点,传入父类AbstractCoroutineContextElement的参数是CoroutineName.Key,只是它可以简写为CoroutineName。其实这也很好理解,在Kotlin中,我们调用伴生对象方法的时候,是可以省去伴生对象的类名的,这里也是同样的道理。

CoroutineName内部声明了一个继承至CoroutineContext.Key的伴生对象Key,并将其作为构造参数传入父类AbstractCoroutineContextElement中,以此作为该协程上下文元素的Key

上面是实现协程上下文元素的一种普遍做法,即在协程上下文元素里面定义一个伴生对象,以伴生对象为Key,标识该上下文元素

最后再看一下CoroutineContext的完整定义:

public interface CoroutineContext {// 根据key获取元素public operator fun <E : Element> get(key: Key<E>): E?// 翻译为"折叠",它与上下文元素的累加有关public fun <R> fold(initial: R, operation: (R, Element) -> R): R// 协程上下文元素的累加public operator fun plus(context: CoroutineContext): CoroutineContext = ...// 当前CoroutineContext中,去掉key标识的元素后,剩下的上下文元素(以CoroutineContext形式返回)public fun minusKey(key: Key<*>): CoroutineContextpublic interface Key<E : Element>public interface Element : CoroutineContext {// 标识上下文元素的Keypublic val key: Key<*>// key相同则返回元素自身,否则返回nullpublic override operator fun <E : Element> get(key: Key<E>): E? =@Suppress("UNCHECKED_CAST")if (this.key == key) this as E else null// 执行传入的operation函数public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =operation(initial, this)public override fun minusKey(key: Key<*>): CoroutineContext =if (this.key == key) EmptyCoroutineContext else this}
}

CoroutineContextplus方法:

public operator fun plus(context: CoroutineContext): CoroutineContext =if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creationcontext.fold(this) { acc, element ->val removed = acc.minusKey(element.key)if (removed === EmptyCoroutineContext) 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) elseCombinedContext(CombinedContext(left, element), interceptor)}}}

为了方便后面阐述,记调用形式为A + B,假设A是含有多个元素的协程上下文,B是单个上下文元素。该方法的大致执行流程如下:

  1. 若元素B是空的,则返回原来的上下文A。
  2. 在fold的lambda块中,可以认为acc为A,element为B。
  3. 若A中减去element.key元素后(记为C),C为空上下文,则返回B(相当于元素B替换了上下文A)。
  4. 查看C中是否有ContinuationInterceptor元素,没有则将C和B拼接后返回。
  5. C中剔除ContinuationInterceptor,记为D,若D是空的,则将B和ContinuationInterceptor拼接然后返回。
  6. D不是空的,则将D和B和ContinuationInterceptor拼接然后返回。

简单来说,这里就是要将「传入的协程上下文元素」与「原来的协程上下文元素」进行拼接,若传入的元素与原来集合中的元素的key有冲突,则用传入的元素替换掉原来集合中key冲突的元素。在上下文元素拼接的时候,若有ContinuationInterceptor元素则要确保它在「协程上下文元素集合」的最右边,这样它的优先级最高,从协程上下文获取该元素的时候可以更快地获取到(至于为什么元素在右边,元素的优先级就高、获取快,在前面介绍CombinedContext中已经说明过了)。

plus方法的执行流程很难用文字叙述清楚,如果想要知道它的实现流程,可以代入几个例子试试。但是它具体的执行流程并不是要分析的重点,有个大概的印象即可。

续体拦截机制

这里算是协程实现原理解析的最后一环了。我们在使用协程的时候,会使用到一些调度器如Dispatchers.MainDispatchers.IO等调度器来调度线程,在前面的分析中并没有提到协程是如何进行线程调度的。

线程的调度与续体拦截器ContinuationInterceptor有关,它也是一种「协程上下文元素」:

public interface ContinuationInterceptor : CoroutineContext.Element {// 续体拦截器对应的Keycompanion object Key : CoroutineContext.Key<ContinuationInterceptor>// 返回一个续体,该续体对原始的续体进行包装(原始的续体作为方法参数传入)。// 如果该方法不想拦截传入的续体,也可以直接返回原来的续体。// 当原始续体完成时,如果该续体之前被拦截了,协程框架会调用releaseInterceptedContinuation// 方法,传入的参数就是「续体的包装类」。public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>// 该函数只有在interceptContinuation成功拦截的情况下,才会被调用。// 若原始续体成功被拦截,当原始续体完成且不再被使用时,该方法会被调用,传入的参数是「续体的包装类」。public fun releaseInterceptedContinuation(continuation: Continuation<*>) {/* do nothing by default */}...
}

续体拦截器可以用于拦截一个续体,最常见的续体拦截器就是协程调度器CoroutineDispatcher,可以通过单例类Dispatchers获取到相应的协程调度器。查看CoroutineDispatcher的实现:

public abstract class CoroutineDispatcher :AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {@ExperimentalStdlibApipublic companion object Key : AbstractCoroutineContextKey<ContinuationInterceptor, CoroutineDispatcher>(ContinuationInterceptor,{ it as? CoroutineDispatcher })    public open fun isDispatchNeeded(context: CoroutineContext): Boolean = truepublic abstract fun dispatch(context: CoroutineContext, block: Runnable)public open fun dispatchYield(context: CoroutineContext, block: Runnable): Unit = dispatch(context, block)public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =DispatchedContinuation(this, continuation)   @InternalCoroutinesApipublic override fun releaseInterceptedContinuation(continuation: Continuation<*>) {(continuation as DispatchedContinuation<*>).reusableCancellableContinuation?.detachChild()}...
}
  • 拦截器:CoroutineDispatcher继承至ContinuationInterceptor,所以它也是一种续体拦截器。
  • 上下文元素的标识:CoroutineDispatcher继承至AbstractCoroutineContextElement,并传入ContinuationInterceptor.Key构造参数,以此来标识自身。
  • isDispatchNeeded:若需要使用dispatch方法进行调度则返回true,否则返回false。该方法默认返回true。协程调度器可以重写该方法,提供一个性能优化以避免不必要的dispatch,例如主线程调度器Dispatchers.Main会判断当前协程是否已经在UI线程中,如果是的话该方法就会返回false,没有必要再去执行dispatch方法进行不必要的线程调度。
  • dispatch:在给定的上下文和线程中,去执行block块。

假设使用的协程调度器是主线程调度器Dispatchers.Main

    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher

查看MainDispatcherLoader.dispatcher

    @JvmFieldval dispatcher: MainCoroutineDispatcher = loadMainDispatcher()private fun loadMainDispatcher(): MainCoroutineDispatcher {return try {val factories = if (FAST_SERVICE_LOADER_ENABLED) {FastServiceLoader.loadMainDispatcherFactory()} else {// We are explicitly using the// `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()`// form of the ServiceLoader call to enable R8 optimization when compiled on Android.ServiceLoader.load(MainDispatcherFactory::class.java,MainDispatcherFactory::class.java.classLoader).iterator().asSequence().toList()}@Suppress("ConstantConditionIf")factories.maxBy { it.loadPriority }?.tryCreateDispatcher(factories)?: createMissingDispatcher()} catch (e: Throwable) {// Service loader can throw an exception as wellcreateMissingDispatcher(e)}}

调用了tryCreateDispatcher

public fun MainDispatcherFactory.tryCreateDispatcher(factories: List<MainDispatcherFactory>): MainCoroutineDispatcher =try {createDispatcher(factories)} catch (cause: Throwable) {createMissingDispatcher(cause, hintOnError())}

继续跟踪,发现createDispatcherMainDispatcherFactory接口的一个方法,其中的一个实现在AndroidDispatcherFactory中:

internal class AndroidDispatcherFactory : MainDispatcherFactory {override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =HandlerContext(Looper.getMainLooper().asHandler(async = true))...
}

HandlerContext其实就是调度器Dispatchers.Main的最终实现:

# handler:主线程的Handler
internal class HandlerContext private constructor(private val handler: Handler,private val name: String?,private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {public constructor(handler: Handler,name: String? = null) : this(handler, name, false)...override fun isDispatchNeeded(context: CoroutineContext): Boolean {return !invokeImmediately || Looper.myLooper() != handler.looper}override fun dispatch(context: CoroutineContext, block: Runnable) {handler.post(block)}    ...
}
  • isDispatchNeeded:通过looper判断协程当前是否在主线程上,是的话返回false,表示不需要再进行线程调度,否则返回true表示需要进行线程调度。

  • dispatch:使用主线程的handler对传入的block块进行post操作。

对「续体拦截器」「协程调度器」有了一定的了解之后,我们再回过头看一下协程调度器是如何发挥作用的。我们前面分析过Cancellable文件的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)}

createCoroutineUnintercepted方法中返回了LaunchLambda实例,在之前的分析中,我们忽略了intercepted方法,直接分析为LaunchLambda会调用resumeCancellableWith方法,若没有为协程设定续体拦截器,那么确实是LaunchLambda会直接调用到resumeCancellableWith方法。我们看看,如果为协程设定了续体拦截器,会发生什么?

查看LaunchLambda调用的intercepted方法,它在IntrinsicsJVM文件中:

public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =(this as? ContinuationImpl)?.intercepted() ?: this

LaunchLambdaContinuationImpl类型,因此会调用到父类ContinuationImpl::intercepted

internal abstract class ContinuationImpl(completion: Continuation<Any?>?,private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {constructor(completion: Continuation<Any?>?) : this(completion, completion?.context)...@Transientprivate var intercepted: Continuation<Any?>? = null    public fun intercepted(): Continuation<Any?> =intercepted?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this).also { intercepted = it }...
}

刚开始interceptednull,所以会判断协程上下文中是否有ContinuationInterceptor元素,若没有则会返回this(即LaunchLambda自身,并将intercepted变量设置为LaunchLambda),有的话则会调用interceptContinuation方法,假设使用的续体拦截器是Dispatchers.Main,那么就是调用到CoroutineDispatcherinterceptContinuation方法,该方法会返回一个DispatchedContinuation(并将DispatchedContinuation设置到intercepted变量中)。

查看CoroutineDispatcher::interceptContinuation

    public final 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 {...}

在这里的例子中,dispatcher就是Dispatchers.Maincontinuation就是LaunchLambda

再回到Cancellable文件的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)}

在有续体拦截器(Dispatchers.Main)的情况下,intercepted方法会返回DispatchedContinuation,接着调用它的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)
}

调用到另外一个resumeCancellableWith方法,这个方法就是在DispatchedContinuation中实现的了:

    inline fun resumeCancellableWith(result: Result<T>,noinline onCancellation: ((cause: Throwable) -> Unit)?) {val state = result.toState(onCancellation)if (dispatcher.isDispatchNeeded(context)) { // 需要线程调度_state = stateresumeMode = MODE_CANCELLABLE// 线程调度,将自身以Runnable块形式传入dispatcher.dispatch(context, this)} else { // 不需要线程调度executeUnconfined(state, MODE_CANCELLABLE) {if (!resumeCancelled(state)) {// 最终会调用continuation.resumeWith,即LaunchLambda.resumeWithresumeUndispatchedWith(result)}}}}

可以看到,它调用了dispatcher.isDispatchNeeded来判断是否需要进行线程调度,以Dispatchers.Main为例,就是判断当前协程是否在主线程中运行,是的话则不需要调度,否则需要将协程调度到主线程中运行。

  • 不需线程调度:最终会调用到LaunchLambda.resumeWith,它后续的执行流程之前已经分析过了。
  • 需要线程调度:(以主线程的协程调度器为例)最终会将传入的Runnable在主线程中执行。

Runnablerun方法在哪实现的呢?在DispatchedContinuation的父类DispatchedTask中有run方法的实现:

    public final override fun run() {...try {// 获取到的delegate其实就是DispatchedContinuationval delegate = delegate as DispatchedContinuation<T>// 获取到的continuation其实就是LaunchLambdaval continuation = delegate.continuationval context = continuation.contextval state = takeState() // NOTE: Must take state in any case, even if cancelledwithCoroutineContext(context, delegate.countOrElement) {val exception = getExceptionalResult(state)/** Check whether continuation was originally resumed with an exception.* If so, it dominates cancellation, otherwise the original exception* will be silently lost.*/val job = if (exception == null && resumeMode.isCancellableMode) context[Job] else nullif (job != null && !job.isActive) {val cause = job.getCancellationException()cancelCompletedResult(state, cause)continuation.resumeWithStackTrace(cause)} else {if (exception != null) {continuation.resumeWithException(exception)} else {// 正常情况下,会执行到这里,调用LaunchLambda的resume方法continuation.resume(getSuccessfulResult(state))}}}} catch (e: Throwable) {...} finally {...}}

run方法中,最终会调用到LaunchLambdaresume方法(内部又会调用到resumeWith方法)。所以这里做的线程调度,其实就是通过主线程的handler,将代码post到主线程中去运行,从而完成线程的调度工作。

另外,还有几个未研究的地方与自己的猜想:

一、releaseIntercepted方法:在BaseContinuationImpl::resumeWith中,每执行完一个续体的invokeSuspend方法,就会调用该续体的releaseIntercepted方法

    protected override fun releaseIntercepted() {val intercepted = intercepted// intercepted不为null且不为自身(即之前成功拦截续体),就进入If块if (intercepted != null && intercepted !== this) {// 调用续体拦截器的releaseInterceptedContinuation方法,并传入续体包装类context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted)}// 将intercepted变量设置为CompletedContinuationthis.intercepted = CompletedContinuation // just in case}

续体拦截器的releaseInterceptedContinuation方法应该是做一些资源清理的工作。

二、像withContext这样的函数:

scope.launch(Dispatchers.Main) {withContext(Dispatchers.IO) {}
}
public suspend fun <T> withContext(context: CoroutineContext,block: suspend CoroutineScope.() -> T
): T {...}

block块执行完后,会将线程自动切回「启动协程时的协程调度器所指定」的线程,那么它是如何切回来的呢?个人猜测,在协程至上而下调用的时候,协程上下文会一层一层地向下传递,withContextblock块执行的时候,协程上下文会被保存在某个地方,等到block块执行结束的时候,会从之前保存的协程上下文中取出协程调度器,将剩余的代码(协程恢复)调度到相应的线程中去执行,从而实现了 block块执行完后,线程会自动切回「启动协程时的协程调度器所指定」的线程。

参考

  1. 协程咖啡厅 - 构造魔法 - 探索 Kotlin 协程实现原理 - M.D。

  2. Suspend functions - Kotlin Vocabulary - YouTube。

Kotlin协程实现原理相关推荐

  1. Kotlin协程 - launch原理 笔记

    一.协程是如何创建的? launch.async 可以创建.启动新的协程,那么协程到底是如何创建的? runBlocking {println(Thread.currentThread().name) ...

  2. pdf 深入理解kotlin协程_Kotlin协程实现原理:挂起与恢复

    今天我们来聊聊Kotlin的协程Coroutine. 如果你还没有接触过协程,推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine? 如果你已经接触过协程,但对协程的原理存 ...

  3. Kotlin 协程,怎么开始的又是怎么结束的?原理讲解!

    九心 | 作者 承香墨影 | 校对 https://juejin.cn/post/6862548590092140558 | 原文 Hi,大家好,这里是承香墨影! 上周我们聊到 Kotlin 协程的使 ...

  4. plsql develop怎么停止job_Kotlin协程实现原理:CoroutineScopeamp;Job

    今天我们来聊聊Kotlin的协程Coroutine. 如果你还没有接触过协程,推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine? 如果你已经接触过协程,但对协程的原理存 ...

  5. 一文看透 Kotlin 协程本质

    前言 公司开启新项目了,想着准备亮一手 Kotlin 协程应用到项目中去,之前有对 Kotlin 协程的知识进行一定量的学习,以为自己理解协程了,结果--实在拿不出手! 为了更好的加深记忆和理解,更全 ...

  6. 枯燥的Kotlin协程三部曲(上)——概念启蒙篇

    0x0.引言 Kotlin 1.3 版本开始引入协程 Coroutine,简练的官方文档和网上一堆浅尝辄止的文章让我心里有些没底,不想止步于仅仅知道: ① Android中,Kotlin协程用于解决: ...

  7. 深入理解Kotlin协程suspend工作原理(初学者也能看得懂)

    1. 概述 挂起函数是Kotlin协程最重要的一个特性,所有其他概念都建立在它的基础上.所以我们需要深入了解它的工作原理. 挂起协程意味着在中间停止它.这类似于玩游戏,当我们想暂停游戏时,可以先存档, ...

  8. 探索 Kotlin 协程原理

    接下来跟大家分享一下我在了解 Kotlin 协程实现的过程中理解的一些概念,如果你发现哪些地方我说错了的话,欢迎提出你的理解. 1. Kotlin 协程原理概述 Kotlin 协程的大致的执行流程如上 ...

  9. Kotlin协程:挂起与恢复原理逆向刨析

    前言:只有在那崎岖的小路上不畏艰险奋勇攀登的人,才有希望达到光辉的顶点. --马克思 前言 经过前面两篇协程的学习,我相信大家对协程的使用已经非常熟悉了.本着知其然更要知其之所以然的心态,很想知道它里 ...

最新文章

  1. 原型、原型对象、构造函数、原型链理解
  2. 中柏平板触摸驱动_一文总览2019年最新最全的工业平板电脑定制化服务
  3. 王者荣耀服务器维护啥时候结束,王者荣耀维护几点结束今天?11月10日维护公告...
  4. LeetCode 1191. K 次串联后最大子数组之和(前缀和+分类讨论)
  5. 如何从一张图片里取出其中一部分_如何鉴别坑人的锌合金龙头
  6. oracle数据库扩容方案_ORACLE数据库扩容
  7. rx2700_第二代锐龙 7 2700X 台式处理器 | AMD
  8. 修复android下webView控件的总结
  9. 控制x86汇编指令eip的方法
  10. 2021华为杯数学建模获奖经验分享
  11. Chrome浏览器离线安装Axure插件
  12. AHCI驱动问题导致Windows 10卡死的解决办法
  13. YOLOv5+TensorRT+Win11(Python版)
  14. 校招和社招有什么区别?不同时期重点不同!
  15. 问题:现有12个外形相同的小球,只有其中一个小球质量不同(不能确定较重还是较轻),请用天平找出是哪个小球不同,而且还要找出究竟是轻是重?条件:只能称三次
  16. 7.25 10figting!
  17. EasyClick 易点云测 IOS版自动化测试工具
  18. 风口中的智慧城市:智慧的面子,数字的里子
  19. 无人机动力测试台-自动化测试系统拉力、扭矩、电压、电流、转速和效率
  20. 前端每日三问#200501怎样在文本框中禁用中文输入法?

热门文章

  1. 临终关怀?抑或一切照旧?PR咋又更新了?
  2. 音视频卡顿问题的原因及解决方案
  3. mysql 复制数据库
  4. 18、弱电工程综合布线系统常用的线材及设备图文资料
  5. Vue 项目中使用Lodop进行打印
  6. Qdown:你值得拥有的全功能文件下载工具
  7. oracle采购业务流程,ORACLE EBS 采购的业务流程
  8. iOS 开发者账号初试
  9. 向量化编程思路小结(矩阵计算)
  10. 阿姆达尔定律(Amdahl’s Law)