Kotlin协程原理解析
协程介绍
Kotlin协程有以下特点:
-
协程可以用同步的代码编写方式编写异步的代码。
举个日常开发的场景:通过网络请求获取数据并渲染到TextView。通过这个例子可以很直观的感受到协程的优点,在处理异步的场景中,协程的代码更为直观优雅。
协程方式
val token = getUserToken() val nickName = getUserNickName(token) textView.text = nickName普通方式
getUserToken() { token -> getUserNickName(token) { nickName -> textView.text = nickName } } -
协程是编程语言的概念,线程是操作系统层面的概念。
-
协程的实现包括了代码转换,线程管理,其底层执行方式依然是Java的线程。有点像对线程池再做一层封装。
-
结构化并发
协程只能运行在指定的协程域,一个协程域可以运行多个协程。当某个协程域的所有的协程运行完成,这个协程域的状态才为完成。
首先先介绍一下协程中的一些基础概念,然后再通过具体例子解析开启协程的原理。
调度器
协程的运行需要调度器,调度器会调度线程执行协程中的任务。
- DEFAULT
默认的调度器,开启协程如果不传入一个调度器的话,默认会用DEFAULT。执行时会使用内部的线程池执行,线程池的线程数等于 CPU 核心数,所以适合CPU密集型计算的任务。 - IO
执行时会使用内部的线程池执行,线程数可以进行动态扩展,适合IO 密集型任务(IO任务主要是等待,不会占用太多CPU资源)。 - MAIN
一个单线程执行任务的调度器。在安卓中,代表在主线程执行任务。
挂起
在协程中,挂起是指一个协程的执行可以在不阻塞线程的情况下暂停和恢复。我们可以用suspend关键字修饰一个函数,表示该函数可能会被挂起。suspend函数不一定会实现真正的挂起,只有释放了当前执行中的线程才是真正的挂起(如使用withContext、delay等)。suspend函数一定要放在suspend函数中执行。
真正的挂起
GlobalScope.launch(Dispatchers.Main) {
//开始挂起
withContext(Dispatchers.IO) {
...
}
}
不能真正挂起
GlobalScope.launch(Dispatchers.Main) {
suspend fun test(): String {
return ""
}
//相当于执行了一个普通方法
test()
}
协程作用域CoroutineScope
用于运行一个新协程的领域类,其本身包含了协程运行的一个全局上下文。
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
可通过CoroutineScope的launch,async等方法开启协程。由于launch需要传入一个扩展函数类型的lambda表达式,在这个lambda内可以直接调用CoroutineScope的方法,所以看起来就像协程运行在一个领域内。这个函数类型是被suspend修饰的函数,所以内部是可以调用其他suspend函数。
GlobalScope.launch {
// TODO: do something
}
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
...
}
Continuation
Continuation表示续体,怎么理解呢,协程运行中当调用调用某个suspend函数进行挂起的时候这个Continuation会参与调度,当调度完成后会通过Continuation的resumeWith恢复挂起点的重新运行。
Continuation的继承树
在后续讲解CPS转换的时候,suspend lambda会转换成横一个SuspendLambda的子类,而suspend方法则会转换成ContinuationImpl的子类。这里可以提前了解一下。
CoroutineContext协程运行上下文
CoroutineContext代表着协程运行的上下文。CoroutineContext可以相加所以它是一个复合的概念,一般来说不同的CoroutineContext会继承CoroutineContext.Element实行相加,在需要使用的场景中通过Key从一个复合的CoroutineContext中取出对应的Element。
常见的Element有:
- CoroutineDispatcher
- Job
- CoroutineName
- CoroutineExceptionHandler
CoroutineContext相加
CoroutineContext可以进行相加,生成一个CombinedContext。如果左右两边的Context的存在同名的Key,右边的会覆盖左边的。对于拦截器会进行特殊处理,拦截器或者包含拦截器的Element始终会在CombinedContext的右边,方便查找(因为查找会优先查找右边)。
public operator fun plus(context: CoroutineContext): CoroutineContext =
//如果右边的Context是空Context,直接返回左边的Context
// fast path -- avoid lambda creation
if (context === EmptyCoroutineContext) this else
context.fold(this) { acc, element ->
//左边的Context移除掉右边Context的Key对应的元素
val removed = acc.minusKey(element.key)
//如果左边的Context只有右边的Context的Key对应的内容,说明右边Contextx包含左边Context的全部内容,直接返回右边的Context
if (removed === EmptyCoroutineContext) element
//左边的Context有其他内容。来到这里的话,如果左右Context都有拦截器,则左边Context的拦截器已经被移除了。以下逻辑会生成一个复合Context,并保证拦截器一定在右边(此拦截器已右边Context的拦截器为优先)
// make sure interceptor is always last in the context (and thus is fast to get when present)
else {
val interceptor = removed[ContinuationInterceptor]
//左边Context没有拦截器,此时左右Context组合成一个复合Context
if (interceptor == null) CombinedContext(removed, element)
//左边Context有拦截器,生成一个复合Context,将拦截器放到右边,其余内容复合起来放到左边
else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor)
else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
cps转换
Kotlin是基于JVM的语言,为了使协程这种基于语言层面的并发设计模式能正常跑在JVM上面,Kotlin编译器会对相关的代码进行CPS转换,进行转换的情况有以下的几种:
suspend CoroutineScope.() -> T
这种类型对应开启协程所传入的一个函数类型的对象block
public fun CoroutineScope.launch(
...
block: suspend CoroutineScope.() -> Unit
): Job {
...
}
转换步骤
- block对象会转换成一个继承SuspendLambda的内部类对象
- 该内部类对象生成状态机逻辑
以下为示例
//源码
class CoroutineDemo {
init {
GlobalScope.launch {
val result1 = getResult1()
val result2 = getResult2()
val result3 = getResult3()
val total = result1 + result2 + result3
}
}
suspend fun getResult1(): String {
delay(1000L)
return "getResult1"
}
suspend fun getResult2(): String {
return "getResult2"
}
suspend fun getResult3() = suspendCancellableCoroutine<String> { cont ->
cont.resume("getResult3")
}
}
//cps转换后的代码(经过简化)
class CoroutineDemo$1 extends SuspendLambda {
Object L$0;
Object L$1;
int label;
final CoroutineDemo this$0;
CoroutineDemo$1(final CoroutineDemo this$0, final Continuation<? super CoroutineDemo$1> continuation) {
super(2, (Continuation) continuation);
this.this$0 = this$0;
}
public final Object invokeSuspend(Object o) {
//函数挂起的标记,此标记为协程内部使用,表示函数真正被挂起
final Object coroutine_SUSPENDED = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0: {
this.label = 1;
Object result1 = this$0.getResult1(this);
if (result1 == coroutine_SUSPENDED) {
return coroutine_SUSPENDED;
} else {
//执行 case 1 的逻辑
invokeSuspend(result1);
}
break;
}
case 1: {
String result1 = (String) o;
this.L$0 = result1;
this.label = 2;
Object result2 = this$0.getResult2(this);
if (result2 == coroutine_SUSPENDED) {
return coroutine_SUSPENDED;
} else {
//执行 case 2 的逻辑
invokeSuspend(result2);
}
break;
}
case 2: {
final String result2 = (String) o;
this.L$1 = result2;
this.label = 3;
Object result3 = this$0.getResult3(this);
if (result3 == coroutine_SUSPENDED) {
return coroutine_SUSPENDED;
} else {
//执行 case 3 的逻辑
invokeSuspend(result3);
}
break;
}
case 3: {
String result1 = (String) this.L$0;
String result2 = (String) this.L$1;
String result3 = (String) o;
String total = result1 + result2 + result3;
break;
}
}
return Unit.INSTANCE;
}
}
suspend方法
转换步骤
- 转换成一个普通的java方法
- 增加一个Continuatio参数
- 返回值改为Object
- 当该方法本身有调用其他的suspend方法时
- 生成一个该方法对应的内部类
- 方法逻辑改为状态机逻辑
转换的思路跟suspend CoroutineScope.() -> T转换的思路是相似的,不同的是suspend CoroutineScope.() -> T的状态机逻辑在invokeSuspend中,而suspend方法的状态机逻辑在对应的java方法的方法体中。
以下为示例
//源码
class CoroutineDemo {
suspend fun getResult(): Int {
val result = getResultA() + getResultB()
return result
}
suspend fun getResultA(): Int {
return 1
}
suspend fun getResultB(): Int {
return 2
}
}
//cps转换后的代码(经过简化)
public class CoroutineDemo {
public final Object getResult(final Continuation continuation) {
CoroutineDemo$getResult$1 getResultContinuation;
//如果不是CoroutineDemo$getResult$1,则continuation为调用getResult的一个外部挂起点
//新建一个CoroutineDemo$getResult$1并持有这个continuation
//当这个方法对应的状态机执行完成了,通过continuation恢复外部挂起点
if (!(continuation instanceof CoroutineDemo$getResult$1)) {
getResultContinuation = new CoroutineDemo$getResult$1(this, continuation);
}
//如果continuation就是CoroutineDemo$getResult$1,则表示继续执行getResult的状态机逻辑
else {
getResultContinuation = (CoroutineDemo$getResult$1) continuation;
}
//函数挂起的标记,此标记为协程内部使用,表示函数真正被挂起
final Object coroutine_SUSPENDED = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (getResultContinuation.label) {
case 0: {
getResultContinuation.L$0 = this;
getResultContinuation.label = 1;
final Object resultA = this.getResultA(getResultContinuation);
if (resultA == coroutine_SUSPENDED) {
return coroutine_SUSPENDED;
} else {
//执行 case 1 的逻辑
getResult(getResultContinuation);
}
break;
}
case 1: {
final CoroutineDemo coroutineDemo = (CoroutineDemo) getResultContinuation.L$0;
final int resultA = (int) getResultContinuation.result;
getResultContinuation.I$0 = resultA;
getResultContinuation.label = 2;
final Object resultB = coroutineDemo.getResultB(getResultContinuation);
if (resultB == coroutine_SUSPENDED) {
return coroutine_SUSPENDED;
} else {
//执行 case 2 的逻辑
getResult(getResultContinuation);
}
break;
}
case 2: {
final int i$0 = getResultContinuation.I$0;
return i$0 + (int) getResultContinuation.result;
}
}
return null;
}
public final Object getResultA(final Continuation<? super Integer> continuation) {
return 1;
}
public final Object getResultB(final Continuation<? super Integer> continuation) {
return 2;
}
}
开启协程
以最简单的开启协程的方式举例
GlobalScope.launch {
//todo
}
开启协程用的是CoroutineScope的launch方法
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
// 1
val newContext = newCoroutineContext(context)
// 2
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
// 3
coroutine.start(start, coroutine, block)
return coroutine
}
1:newCoroutineContext(context)将传入的Context与当前协程的Context合并
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
// 1.1
val combined = foldCopies(coroutineContext, context, true)
// 1.2
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
// 1.3
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
debug + Dispatchers.Default else debug
}
1.1:一般情况下,就是将传入的Context与当前协程的Context相加
1.2:非DEBUG模式,直接使用combined
1.3:确保了返回的Context一定有拦截器(一般的调度器都是拦截器,如:Dispatchers.Default),如果没有拦截器就给combined加一个Dispatchers.Default
2:默认情况下,创建一个新的StandaloneCoroutine协程实例
该实例会关联父协程的作用域(此处没有父协程,所以忽略这一层),并且提供后续步骤所需要的上下文。
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
3:coroutine.start(start, coroutine, block)默认情况下会调用CoroutineStart.DEFAULT的invoke方法
//StandaloneCoroutine的start方法
public abstract class AbstractCoroutine<in T>(
parentContext: CoroutineContext,
initParentJob: Boolean,
active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
start(block, receiver, this)
}
}
//StandaloneCoroutine的start方法会调用CoroutineStart.DEFAULT的invoke方法
public enum class CoroutineStart {
DEFAULT,
...
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
}
}
进入block.startCoroutineCancellable(receiver, completion)
注意这里的receiver和completion都为刚刚创建的StandaloneCoroutine协程实例
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
receiver: R,
completion: Continuation<T>,
onCancellation: ((cause: Throwable) -> Unit)? = null
) = runSafely(completion) {
// 1
createCoroutineUnintercepted(receiver, completion)
// 2
.intercepted()
// 3
.resumeCancellableWith(Result.success(Unit), onCancellation)
}
1:createCoroutineUnintercepted(receiver, completion)创建SuspendLambda对象
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> {
// 1.1
val probeCompletion = probeCoroutineCreated(completion)
return if (this is BaseContinuationImpl)
// 1.2
create(receiver, probeCompletion)
else {
createCoroutineFromSuspendFunction(probeCompletion) {
(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
}
}
}
1.1:probeCoroutineCreated为调试所用的api,正式环境会直接返回completion。所以probeCompletion就是completion。
1.2:this即(suspend R.() -> T)类型的当前对象,也就是通过launch{}开启协程所传入的一个函数类型对象。前面介绍CPS转换的时候说过,(suspend R.() -> T)类型在编译的时候会转换成SuspendLambda的子类,SuspendLambda的基类是BaseContinuationImpl所以会走create方法。
在BaseContinuationImpl中create并没有实现,具体的实现在CPS转换后生成的类中
internal abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) {
public open fun create(value: Any?, completion: Continuation<*>): Continuation<Unit> {
throw UnsupportedOperationException("create(Any?;Continuation) has not been overridden")
}
}
以下是某个(suspend R.() -> T)类型经过CPS转换后的类,可以看到create方法就是调用了基类的两个参数的构造。
final class MainActivity$onCreate$1 extends SuspendLambda implements Function2 {
int I$0;
Object L$0;
Object L$1;
int label;
final MainActivity this$0;
MainActivity$onCreate$1(MainActivity var1, Continuation var2) {
super(2, var2);
this.this$0 = var1;
}
public final Continuation create(Object var1, Continuation var2) {
return (Continuation)(new MainActivity$onCreate$1(this.this$0, var2));
}
...
}
继续往上跟踪,SuspendLambda的两个参数构造又网上调用了ContinuationImpl的一个参数的构造。
internal abstract class SuspendLambda(
public override val arity: Int,
completion: Continuation<Any?>?
) : ContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction {
...
}
再继续往上追踪,ContinuationImpl的一个参数的构造会调用自身两个参数的构造并将传入的completion的Context保存起来。
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!!
...
}
2:intercepted()生成DispatchedContinuation
ContinuationInterceptor是一个CoroutineContext.Element,也就是context内的组成元素。 context[ContinuationInterceptor]这种形式的代码可以从CoroutineContext获取到其中的ContinuationInterceptor。接着调用这个ContinuationInterceptor的interceptContinuation方法并把this作为参数传入。
internal abstract class ContinuationImpl(
completion: Continuation<Any?>?,
private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
public fun intercepted(): Continuation<Any?> =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
}
这个ContinuationInterceptor就是开启协程传入的Dispatchers.Default,interceptContinuation的具体实现在Dispatchers.Default的基类CoroutineDispatcher中,可见这个方法返回了一个DispatchedContinuation。
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation)
}
3:resumeCancellableWith使用调度器执行逻辑代码
intercepted()中返回的DispatchedContinuation会执行其resumeCancellableWith方法.在Dispatchers.Default的实现中,isDispatchNeeded直接是返回true,所以会走if分支.接下来我们看下dispatch方法做了什么。
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)
}
}
}
}
dispatch的实现在SchedulerCoroutineDispatcher,SchedulerCoroutineDispatcher是Dispatchers.Default的基类.可以看到dispatch被一个CoroutineScheduler对象接管了。
internal open class SchedulerCoroutineDispatcher(
private val corePoolSize: Int = CORE_POOL_SIZE,
private val maxPoolSize: Int = MAX_POOL_SIZE,
private val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,
private val schedulerName: String = "CoroutineScheduler",
) : ExecutorCoroutineDispatcher() {
// This is variable for test purposes, so that we can reinitialize from clean state
private var coroutineScheduler = createScheduler()
private fun createScheduler() =
CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
override fun dispatch(context: CoroutineContext, block: Runnable): Unit = coroutineScheduler.dispatch(block)
}
继续走进CoroutineScheduler的dispatch方法。这个方法主要是将block封装成一个task并交由CoroutineScheduler调度执行。调度的机制跟Java的线程池类似,这里不再展开。重点关注3.1中封装的task。
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
trackTask()
//3.1
val task = createTask(block, taskContext)
//获取当前线程的Worker
val currentWorker = currentWorker()
//尝试加入Worker的任务队列
val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch)
//notAdded != null表示的是加入任务队列失败
if (notAdded != null) {
//尝试加入全局任务队列
if (!addToGlobalQueue(notAdded)) {
// Global queue is closed in the last step of close/shutdown -- no more tasks should be accepted
throw RejectedExecutionException("$schedulerName was terminated")
}
}
//以上代码用于确保将任务加入到队列,以下代码取保有足够的工作线程
val skipUnpark = tailDispatch && currentWorker != null
if (task.mode == TASK_NON_BLOCKING) {
if (skipUnpark) return
signalCpuWork()
} else {
// Increment blocking tasks anyway
signalBlockingWork(skipUnpark = skipUnpark)
}
}
可以看到createTask返回的是一个Task的子类TaskImpl,而Task本身是个Runnable。当这个Task被执行时,会执行它所持有的block的run方法,这个block就是resumeCancellableWith方法中传入 CoroutineScheduler的dispatch方法的参数this。所以block就是DispatchedContinuation。
fun createTask(block: Runnable, taskContext: TaskContext): Task {
val nanoTime = schedulerTimeSource.nanoTime()
if (block is Task) {
block.submissionTime = nanoTime
block.taskContext = taskContext
return block
}
return TaskImpl(block, nanoTime, taskContext)
}
internal class TaskImpl(
@JvmField val block: Runnable,
submissionTime: Long,
taskContext: TaskContext
) : Task(submissionTime, taskContext) {
override fun run() {
try {
block.run()
} finally {
taskContext.afterTask()
}
}
}
这个block也就是DispatchedContinuation并不是直接继承Runnable,而是通过继承DispatchedTask间接继承Runnable,以下是看DispatchedTask的run方法。在协程正常执行的情况下代码会走到3.1处,resume是Continuation的扩展方法,最终会执行Continuation的resumeWith。在这段代码里continuation这个对象是在DispatchedContinuation构造的时候传入的,也就是本次示例中的MainActivity1。
public final override fun run() {
assert { resumeMode != MODE_UNINITIALIZED } // should have been set before dispatching
val taskContext = this.taskContext
var fatalException: Throwable? = null
try {
val delegate = delegate as DispatchedContinuation<T>
val continuation = delegate.continuation
withContinuationContext(continuation, delegate.countOrElement) {
val context = continuation.context
val state = takeState() // NOTE: Must take state in any case, even if cancelled
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 null
if (job != null && !job.isActive) {
val cause = job.getCancellationException()
cancelCompletedResult(state, cause)
continuation.resumeWithStackTrace(cause)
} else {
if (exception != null) {
continuation.resumeWithException(exception)
} else {
//3.1
continuation.resume(getSuccessfulResult(state))
}
}
}
} catch (e: Throwable) {
// This instead of runCatching to have nicer stacktrace and debug experience
fatalException = e
} finally {
val result = runCatching { taskContext.afterTask() }
handleFatalException(fatalException, result.exceptionOrNull())
}
}
resume是Continuation的扩展方法,相当于调用了resumeWith。
/**
* Resumes the execution of the corresponding coroutine passing [value] as the return value of the last suspension point.
*/
@SinceKotlin("1.3")
@InlineOnly
public inline fun <T> Continuation<T>.resume(value: T): Unit = resumeWith(Result.success(value))
resumeWith是BaseContinuationImpl中的方法,BaseContinuationImpl也是所有开启协程传入的lambda通过cps转换后生成的类的基类(在本例中就是MainActivity1)。可以看到方法中开启了一个循环,先执行自身的invokeSuspend方法获取一个结果,再调用上游的Continuation的invokeSuspend。
internal abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
public final override fun resumeWith(result: Result<Any?>) {
var current = this
var param = result
while (true) {
probeCoroutineResumed(current)
with(current) {
val completion = completion!!
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.resumeWith(outcome)
return
}
}
}
}
...
}
withContext切换调度
在suspend方法中,可通过withContext并传入一个suspend lamda进行一个调度器的切换。以下是withContext的源码,这里我忽略了相同调度器的情况,重点看withConetext切换另外的调度器的情况。
withContext源码
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
//1
return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
// compute new context
//2
val oldContext = uCont.context
// Copy CopyableThreadContextElement if necessary
//3
val newContext = oldContext.newCoroutineContext(context)
...
// SLOW PATH -- use new dispatcher
//4
val coroutine = DispatchedCoroutine(newContext, uCont)
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()
}
}
1:获取外部Continuation
withContext本身就是一个suspend方法,所以通过cps转换的时候参数本来就会新增一个Continuation,suspendCoroutineUninterceptedOrReturn相当于让开发者可以在代码编写的时候就用到这个Continuation。
2:获取外部Continuation的Context
从传入的Continuation中获取Context,即挂起点的Context。
3:合并Context
外部Continuation的Context与传入的Context合并,调度器会优先使用传入的Context中的调度器。
4:开始调度
在介绍GlobalScope.launch的时候,同样会使用block调用startCoroutineCancellable构建自身并持有着一个上游的Continuation,区别只是GlobalScope.launch使用的是StandaloneCoroutine,withContext用的则是DispatchedCoroutine。而startCoroutineCancellable最终会通过调度执行到block所代表的BaseContinuationImpl的resumeWith点击跳转回顾,resumeWith最终又会执行上游Continuation的resumeWith。所以接下来我们来看一下DispatchedCoroutine和StandaloneCoroutine。
DispatchedCoroutine与StandaloneCoroutine
AbstractCoroutine的继承树
从继承关系可以看出,DispatchedCoroutine与StandaloneCoroutine都有共同的基类AbstractCoroutine并且AbstractCoroutine重写了resumeWith,从源码可以看到当resumeWith被调用后,就会执行afterResume。DispatchedCoroutine与ScopeCoroutine并没有重写resumeWith,所以也是同样的逻辑。我们重点关注afterResume这个方法。
public abstract class AbstractCoroutine<in T>(
parentContext: CoroutineContext,
initParentJob: Boolean,
active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
/**
* Completes execution of this with coroutine with the specified result.
*/
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)
...
}
DispatchedCoroutin继承ScopeCoroutine,ScopeCoroutine继承AbstractCoroutine,ScopeCoroutine和DispatchedCoroutin都重写了afterResume,所以以DispatchedCoroutin为准就好。
从这里可分析出,使用withContext切换了调度器执行之后,当调度器执行完block内的逻辑后会通过DispatchedCoroutine的afterResume重新开始挂起点处的调度。
internal class DispatchedCoroutine<in T> internal constructor(
context: CoroutineContext,
uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
@JvmField
public val _decision = atomic(UNDECIDED)
private fun trySuspend(): Boolean {
_decision.loop { decision ->
when (decision) {
UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, SUSPENDED)) return true
RESUMED -> return false
else -> error("Already suspended")
}
}
}
private fun tryResume(): Boolean {
_decision.loop { decision ->
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 // completed before getResult invocation -- bail out
// Resume in a cancellable way because we have to switch back to the original dispatcher
uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
}
internal fun getResult(): Any? {
if (trySuspend()) return COROUTINE_SUSPENDED
// otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state
val state = this.state.unboxState()
if (state is CompletedExceptionally) throw state.cause
@Suppress("UNCHECKED_CAST")
return state as T
}
}
而GlobalScope.launch所对应StandaloneCoroutine因为他本身就是协程的入口,不存在挂起点的概念,所以并不需要切回原调度器,这里需要注意的一点就是afterCompletion是JobSupport的方法并且只是个空实现。
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}