• 一、Continuation Passing Style (CPS)
    • 1、原理
    • 2、续体在kotlin中的声明
  • 二、State Machine
    • 1、续体+状况机
    • 2、这两个东西合作能够完成什么?
  • 三、launch履行进程
    • 1、全体流程
    • 2、createCoroutineUnintercepted办法
    • 3、协程创立履行进程剖析
      • (1)模仿launch进程建议协程
      • (2)反编译检查创立进程
      • (3)拓宽:生成SuspendLambda的办法
  • 四、delay履行进程
  • 五、协程品种及联系
  • 六、协程上下文

(内容略长,前三点是要点,四五六点主张结合源码观看)

一、ContinuationPassingStyle(CPS)

协程在多种语言中都有运用,那它们通用的辅导概念是什么?那便是CPS

1、原理

(1)举个比如,平时运用的直接编码风格Direct Style

// 三个进程,发送一个token
fun postItem(item: Item) {
    val token = generateToken()         // step1
    val post = createPost(token, item)  // step2
    processPost(post)                   // step3
}

CPS的 C 是Continuation,是续体的意思,上面代码中 step2、step3 是 step1 的续体,step3 是 step2 的续体

(2)将上述代码进行CPS转化后,变成传接续体风格Continuation Passing Style

fun postItem(item: Item) {
    generateToken { token ->              // setp1
        createPost(token, item) { post -> // step2
            processPost(post)             // step3
        }
    }
}

这个变化后能够发现,CPS转化其实是将下一步变成了回调。定论:CPS == Callbacks

(3)Kotlin 提供了一种Direct Style的写法,而且一起能完成CPS的效果。为什么能这么完成?由于 kotlin在编译的时分替咱们完成了回调。看个比如,咱们将上面的比如写成kotlin的代码:

suspend fun postItem(item: Item) {
    val token = generateToken()         // 在上面的比如中需求传送续体(callback),所以这个办法也是 suspend 办法
    val post = createPost(token, item)  // 也是 suspend 办法
    processPost(post)
}

将上述代码编译成Java后,会发现带suspend的办法 在编译后办法签名都会增加一个续体参数,下面比照一下

// 编译前:Kotlin
suspend fun createPost(token: Token, item: Item): Post { … }
// 编译后:Java/JVM(cont其实是一个callback,Post为成果)
Object createPost(Token token, Item item, Continuation<Post> cont) { … }

回到上面的举例代码,其间前两步每一步都会产生成果,而每一步的成果都需求传递给下一步,所以能够思考一下,续体的效果是什么?

总结:续体担任将当时进程成果传递给下个进程,一起移送下一步的调用权

调用权是怎么产生的?是由于咱们将下一步的代码打包成了目标,持续传递下去,所以持续履行下一步的调用权会移送给后边履行的代码(这才是异步履行的中心思想)。

2、续体在kotlin中的声明

下面是Kotlin中续体接口,看下这个接口的注释,suspension point便是标示suspend的办法

Kotlin协程完全解析

这个接口内有一个目标和一个办法:

  • CoroutineContext:是一个链表结构,能够运用「+」操作符,其间包含协程履行时需求的一些参数:称号/ID、调度器 Dispatcher、操控器 Job、反常 Handler等(把 Job 称为「操控器」感觉好了解一些)
  • resumeWith:触发下一步的办法,参数 result 是当时进程的成果(上面说到了调用权resumeWith便是调用进口)

「Continuation is a generic callback interface」: Continuation是一个续体通用的回调接口

二、StateMachine

状况机是一个能够操控状况的目标

续体会缓存成果递交给下一步,状况机会缓存进程编号,并在每一步触发的时分将状况改为下一步,以完成进程切换

1、续体+状况机

kotlin中的suspend办法终究会被编译成一个状况机,下面举个比如,来看看suspend办法是如何转化的

(1)咱们先给每个进程编号:

suspend fun postItem(item: Item) {
    switch (label) {
        case 0:
            generateToken()
        case 1:
            createPost(token, item)
        case 2:
            processPost(post)
    }
}

(2)然后进行CPS转化,加上续体,完成成果传递、调用权转移;一起看到转化后有个「resume」办法,每一次触发下一步都会调用该办法,一起在「postItem」内完成状况机,保证每次调用会触发下一进程

// 进口:postItem
fun postItem(item: Item, cont: Continuation) {
    val sm = object : CoroutineImpl(cont) { // cont是父协程的续体,在当时协程完毕时会触发cont的resume
        fun resume(...) {
            postItem(null, this)    // 续体回调进口
        }
    }
    switch (sm.label) {
        case 0:
            sm.item = item      // 初始参数,履行进程中传递的一些参数
            sm.label = 1        // 状况操控
            requestToken(sm)    // step1,传入续体,履行完成后调用resume,并将成果传递下去
        case 1:
            val token = sm.result as Token  // 从续体中拿取上一步的成果
            val item = sm.item              // 从续体中拿取初始参数
            sm.label = 2                    // 状况操控
            createPost(token, item, sm)     // step2,传入续体&参数,履行完成后调用resume,并将成果传递下去
        case 2:
            val post = sm.result as Post    // 从续体中拿取上一步的成果
            processPost(post, sm)           // spte3
    }
}

细心看上面的额注释,幻想一下履行进程,首先进入「case 0」然后「label」变成1,在「requestToken」办法内调用一次「sm」目标的「resume」,再次进入「postItem」,创立新的「sm」,此时假定新的「sm」会承继「cont」的数据,那么会进入「case 1」……

再复习一下:

续体传递成果,移送下一步调用权

状况机完成每调用一次同一个办法,就履行一个进程

2、这两个东西结合能够完成什么?

有了续体&状况机咱们能够做什么?

╮( ̄▽  ̄)╭「在恣意的时分建议下一步」

幻想一下,假如我只会调用「resumeWith」…… 没联系,续体&状况机帮咱们完成了悉数(参数传递、进程切换),咱们只用在恣意时刻恣意线程中调用「resumeWith」来触发下一步

三、launch履行进程

1、全体流程

理解什么是续体状况机后,咱们来看一下协程建议的进程,从CoroutineScope.launch看起:

Kotlin协程完全解析

源码就不逐个截图了,全体流程如下图,从左上角开端:

Kotlin协程完全解析

上图浅赤色的是要点,后续会要点打开讲createCoroutineUnintercepted的反编译的代码,先看一下上图的几个要害目标:

(1)SuspendLambda

SuspendLambda是一个续体的完成,下面是SuspendLambda承继联系,能够看到它是现已完成了resumeWith办法了

Kotlin协程完全解析

(2)Dispatcher

Dispatcher续体分发器,它有多种完成,最简略的便是Dispatchers.Main,咱们也从这个源码开端看起。咱们先来看下承继联系,能够先简略的以为,各类Dispatcher的爷爷便是ContinuationInterceptor,爹便是CoroutineDispatcher

Kotlin协程完全解析

上图中有两种Dispatcher,其间HandlerContextdispatch完成会回调到主线程:

Kotlin协程完全解析

(3)DispatchedContinuation

DispatchedContinuation也是续体,不过它是一个续体的托付类,内部持有一个续体目标。来看一下DispatchedContinuation的承继联系,SchedulerTaskrun办法中调用「resume相关办法 & 反常Handler」

Kotlin协程完全解析

由于续体resumeWith都交由DispatchedContinuationrun办法调用,所以会称之为续体的托付操控者

Kotlin协程完全解析

(看到这个by是不是有一种茅塞顿开的感觉,可读性大大提高)

2、反编译createCoroutineUnintercepted办法

其他办法根本都有源码可看,就这个办法隐藏的特别深,终究找到kotlin开源项目中的代码:kotlin/IntrinsicsJvm.kt at master JetBrains/kotlin (github.com)

Kotlin协程完全解析

来看下这个办法的说明:

  1. Creates unintercepted coroutine with receiver type [R] and result type [T]. 运用receiver和result的类型创立一个不行打断的协程(receiver能够忽略,重视result即可)

  2. This function creates a new, fresh instance of suspendable computation every time it is invoked. 这个办法会创立suspend相关逻辑(中心逻辑实际上是编译器创立的,这儿边只履行一个new)

  3. To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance. 经过resume办法发动协程

  4. The [completion] continuation is invoked when coroutine completes with result or exception. completion是一个续体,在这儿创立的协程完毕或许反常时会被调用

先说一个定论,图中的「this」是一个承继BaseContinuationImpl的目标,为什么呢,接着看下面反编译剖析

3、协程创立履行进程剖析

(1)模仿launch进程建议协程

由于直接反编译规范协程代码并不能非常直接的看到调用进程,所以咱们依据createCoroutineUnintercepted的解说建议协程

import kotlinx.coroutines.delay
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.createCoroutineUnintercepted
fun main() {
    launch3 {
        print("before")
        delay(1_000)
        print("\nmiddle")
        delay(1_000)
        print("\nafter")
    }
    Thread.sleep(3_000)
}
fun <T> launch3(block: suspend () -> T) {
    // 1、传入代码块block,运用block创立协程,
    // 2、一起自行创立一个续体,「resumeWith」终究会被调用
    val coroutine = block.createCoroutineUnintercepted(object : Continuation<T> {
        override val context: CoroutineContext
            get() = EmptyCoroutineContext
        override fun resumeWith(result: Result<T>) {
            println("\nresumeWith=$result")
        }
    })
    // 3、履行block协程
    coroutine.resume(Unit)
}

承认履行成果:

before
middle
after
resumeWith=Success(kotlin.Unit)

(2)反编译检查创立进程

以上操作首要为了能更清晰的看到反编译的代码,咱们运用dx2jar进行反编译,看以下代码中注释的1-3步

import kotlin.Metadata;
import kotlin.Result;
import kotlin.ResultKt;
import kotlin.Unit;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;
import kotlin.coroutines.EmptyCoroutineContext;
import kotlin.coroutines.intrinsics.IntrinsicsKt;
import kotlin.coroutines.jvm.internal.DebugMetadata;
import kotlin.coroutines.jvm.internal.SuspendLambda;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.internal.Intrinsics;
import kotlinx.coroutines.DelayKt;
public final class KTest6Kt {
  public static final void main() {
    // 1、编译后,代码块变为承继「SuspendLambda」的目标,该目标还完成了Function接口,是一个「续体+状况机」的目标
    launch3(new KTest6Kt$main$1(null));
    Thread.sleep(3000L);
  }
  public static final <T> void launch3(Function1<? super Continuation<? super T>, ? extends Object> paramFunction1) {
    Intrinsics.checkNotNullParameter(paramFunction1, "block");
    // 2、「createCoroutineUnintercepted」会创立一个新的「KTest6Kt$main$1」目标(为什么?),传入咱们自定义的续体
    Continuation continuation = IntrinsicsKt.createCoroutineUnintercepted(paramFunction1, new KTest6Kt$launch3$coroutine$1());
    Unit unit = Unit.INSTANCE;
    Result.Companion companion = Result.Companion;
    // 3、发动协程
    continuation.resumeWith(Result.constructor-impl(unit));
  }
  // 咱们自定义的续体的内部类
  public static final class KTest6Kt$launch3$coroutine$1 implements Continuation<T> {
    public CoroutineContext getContext() {
      return (CoroutineContext)EmptyCoroutineContext.INSTANCE;
    }
    public void resumeWith(Object param1Object) {
      StringBuilder stringBuilder = new StringBuilder();
      stringBuilder.append("\nresumeWith=");
      stringBuilder.append(Result.toString-impl(param1Object));
      param1Object = stringBuilder.toString();
      System.out.println(param1Object);
    }
  }
  // 协程「续体+状况机」的内部类
  static final class KTest6Kt$main$1 extends SuspendLambda implements Function1<Continuation<? super Unit>, Object> {
    int label;
    KTest6Kt$main$1(Continuation param1Continuation) {
      super(1, param1Continuation);
    }
    public final Continuation<Unit> create(Continuation<?> param1Continuation) {
      Intrinsics.checkNotNullParameter(param1Continuation, "completion");
      return (Continuation<Unit>)new KTest6Kt$main$1(param1Continuation);
    }
    public final Object invoke(Object param1Object) {
      return ((KTest6Kt$main$1)create((Continuation)param1Object)).invokeSuspend(Unit.INSTANCE);
    }
    // 4、resumeWith触发
    public final Object invokeSuspend(Object param1Object) {
      Object object = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      int i = this.label;
      if (i != 0) {
        if (i != 1) {
          if (i == 2) {
            ResultKt.throwOnFailure(param1Object);
          } else {
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
          }
        } else {
          ResultKt.throwOnFailure(param1Object);
          param1Object = this;
          System.out.print("\nmiddle");
          ((KTest6Kt$main$1)param1Object).label = 2;
        }
      } else {
        ResultKt.throwOnFailure(param1Object);
        param1Object = this;
        System.out.print("before");
        ((KTest6Kt$main$1)param1Object).label = 1;
        if (DelayKt.delay(1000L, (Continuation)param1Object) == object)
          return object;
        param1Object = this;
        System.out.print("\nmiddle");
        ((KTest6Kt$main$1)param1Object).label = 2;
      }
      System.out.print("\nafter");
      return Unit.INSTANCE;
    }
  }
}

ps:对于两个参数的重载办法,能够看到第一个参数并没有用:

Kotlin协程完全解析

其间第3步的resumeWith办法完成在哪里?咱们看下上面的SuspendLambda的承继联系图即可知道是BaseContinuationImpl # resumeWith办法:

internal abstract class BaseContinuationImpl(
    // 创立时传入,完成时调用。在上面的比如中,这是咱们自定义的续体,在「KTest6Kt$main$1 # create」办法中传入
    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 resume
        // unrolling:铺开,这儿表示铺开递归,运用循环代替递归,减少栈深度
        //「this」即block转化为的「SuspendLambda」目标,即「KTest3Kt$main$1」目标
        var current = this
        //「result」一般默许是「Result.success(Unit)」,上面的比如中是「Unit」
        var param = result
        while (true) {
            // Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
            // can precisely track what part of suspended callstack was already resumed
            probeCoroutineResumed(current)
            with(current) {
                // 获取「current」的「completion」
                // 在上面的比如中,「completion」是内部协程目标,即「KTest3Kt$launch$coroutine$1」目标
                val completion = completion!! // fail fast when trying to resume continuation without completion
                //「invokeSuspend」获取履行成果
                val outcome: Result<Any?> =
                    try {
                        val outcome = invokeSuspend(param)
                        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) {
                    // unrolling recursion via loop
                    // 假如用于完成的续体内还有 completion,则开端套娃,直至获取到最外层的「续体」
                    // (「BaseContinuationImpl」是「SuspendLambda」的父类,只要「CPS」的时分会生成)
                    // 「suspend」办法中有「suspend」办法时会触发这儿的逻辑
                    current = completion
                    param = outcome
                } else {
                    // top-level completion reached -- invoke and return
                    // 在默许状况下,最外部协程的续体是「StandaloneCoroutine」
                    // 上面的比如中,最外部的是咱们自己声明的匿名内部续体(object : Continuation<T>)
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }
    ......
}

「resumeWith」→ 「invokeSuspend」→

(1)invokeSuspend回来COROUTINE_SUSPENDED,return完毕当时resumeWith,等候下次resumeWith

(2)invokeSuspend回来其他或许反常,生成Result目标,假如completion仍是BaseContinuationImpl目标,则套娃,否则调用completion续体的resumeWith

这儿套娃的状况出现在suspend办法中还有suspend办法,completion实际上是父协程的续体,即续体里还有续体

(3)拓宽:生成SuspendLambda的办法

  • kotlin/SuspendLambdaLowering.kt at master JetBrains/kotlin (github.com)
  • SuspendLambdaLowering #generateContinuationClassForLambda
  • kotlin/JvmSymbols.kt at master JetBrains/kotlin (github.com)

Kotlin协程完全解析


以上要点已讲完,下面最重要的便是那两张UML图


四、delay履行进程

续体有深入了解后,咱们再来看看下面比如中delay的履行进程

GlobalScope.launch(Dispatchers.Main) {
    print("before")
    delay(1_000)
    print("\nmiddle")
    delay(1_000)
    println("\nafter")
}

delay办法:

public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

suspendCancellableCoroutine效果:获取一个续体后,传入scheduleResumeAfterDelay办法

看一下suspendCancellableCoroutine的内容,其间经过cancellable.getResult() → trySuspend()回来COROUTINE_SUSPENDED停止当时履行,等候下次resume

COROUTINE_SUSPENDED 这个标志代表 return 当时办法,履行权力交由下次调用续体 resumeWith 的目标

public suspend inline fun <T> suspendCancellableCoroutine(
    crossinline block: (CancellableContinuation<T>) -> Unit
): T =
    // Obtains the current continuation instance inside suspend functions
    suspendCoroutineUninterceptedOrReturn { uCont ->
        // 创立「CancellableContinuationImpl」
        val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
        /*
         * For non-atomic cancellation we setup parent-child relationship immediately
         * in case when `block` blocks the current thread (e.g. Rx2 with trampoline scheduler), but
         * properly supports cancellation.
         */
        cancellable.initCancellability()
        block(cancellable)      // block内发送推迟音讯,发送完后当时调用栈即可完毕,即可「return」
        cancellable.getResult() // 首次调用,回来「COROUTINE_SUSPENDED」,「COROUTINE_SUSPENDED」便是「return」
    }

其间atomic操作开源代码:kotlinx.atomicfu/AtomicFU.kt…

回到delay办法中,其间context.delay从context中获取ContinuationInterceptor(该类是各种Dispatcher的基类),反回空就运用DefaultDelay

internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay

本例中运用的是Dispatcher.Main,这儿获取的便是HandlerContext目标,能够看到HandlerContext中的scheduleResumeAfterDelay办法

override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
    val block = Runnable {
        with(continuation) { resumeUndispatched(Unit) }
    }
    if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) {
        continuation.invokeOnCancellation { handler.removeCallbacks(block) }
    } else {
        cancelOnRejection(continuation.context, block) // 发送失败就 cancel
    }
}

运用handler.postDelayed发送推迟音讯,音讯体中触发CancellableContinuationImpl续体的resumeUndispatched办法

override fun CoroutineDispatcher.resumeUndispatched(value: T) {
    val dc = delegate as? DispatchedContinuation
    resumeImpl(value, if (dc?.dispatcher === this) MODE_UNDISPATCHED else resumeMode)
}

delegate是啥?能够看下CancellableContinuationImpl的结构办法

Kotlin协程完全解析

在前面suspendCancellableCoroutine办法中能够看到传入的是uCont.intercepted(),即当时协程的续体再转化成DispatchedContinuation目标

假如必定要看到uCont是什么,能够看到咱们第一次反编译的代码delegate便是KTest6Kt$main$1目标,便是传入「delay」办法的续体,便是「SuspendLambda」

下一行中的dispatcher便是当时协程的调度器Dispatcher.Main,意思是假如resume和launch的dispatcher是同一个,则传入undispatched的状况,这儿便是同一个

回来再看CancellableContinuationImpl # resumImpl,其间的操作是:假如当时是NoCompleted状况则调用dispatchResume办法

Kotlin协程完全解析

调用进程如下:

CancellableContinuationImpl # resumImpl// NotCompleted
CancellableContinuationImpl # dispatchResume// tryResume 回来false
DispatchedTask<T>.dispatch
↓ // mode == MODE_UNDISPATCHED
DispatchedTask<T>.resume
↓
DispatchedContinuation # resumeUndispatchedWith
↓
Continuation # resumeWith

假如本例中运用的是Dispatcher.IO,则context.delay获取的是DefaultDelay

Kotlin协程完全解析

看到EventLoopImplBase中的完成


public override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
    val timeNanos = delayToNanos(timeMillis)
    if (timeNanos < MAX_DELAY_NS) {
        val now = nanoTime()
        DelayedResumeTask(now + timeNanos, continuation).also { task ->
            continuation.disposeOnCancellation(task)
            schedule(now, task)
        }
    }
}

这儿运用了DelayedResumeTaqsk进行了推迟履行resumeWith,能够看到续体仍是一路被携带的,我们能够自行剖析下调用进程。

五、协程品种及联系

Kotlin协程完全解析

  1. Job提供协程的根本操作:start、cancel、join,且声明一个子Job序列:children

a job is a cancellable thing with a life-cycle that culminates in its completion.

  1. JobSupport完成Job的根本办法,完成父子联系、状况操控(父Job撤销,子Job悉数撤销;子Job反常,父子Job悉数撤销,除SupervisorCoroutine

A concrete implementation of [Job]. It is optionally a child to a parent job.

  1. AbstractCoroutine,在JobSupport基础上增加协程上下文、resumeWith办法、生命周期回调办法

Abstract base class for implementation of coroutines in coroutine builders.

4、各类协程完成

办法名 完成 效果 要害操作、原理
CoroutineScope.launch StandaloneCoroutine 建议非堵塞协程,回来操控器Job 使命调度(线程池+Handler)
CoroutineScope.runBlocking BlockingCoroutine 建议堵塞协程 LockSupport.parkNanos挂起当时线程
CoroutineScope.async DeferredCoroutine 建议非堵塞协程,回来Deferred CancellableContinuationImpl.getResult办法回来 COROUTINE_SUSPENDED 挂起父协程
coroutineScope ScopeCoroutine 承继外部context,回来新的操控器Job 新建一个新的协程
supervisorScope SupervisorCoroutine 一个子协程反常不会影响到其他子协程 新建一个协程,重写 childCancelled 办法
withContext ScopeCoroutineDispatchedCoroutineUndispatchedCoroutine 在当时协程基础上运用新的context发去协程 比照当时协程context与新传入的context,判断运用那种协程,……
withTimeout TimeoutCoroutine 超时自动cancel协程 各种Dispatcher都有自己的完成
CoroutineScope ContextScope 回来一个只完成了context的scope
  1. Job状况操控

JobSupport中的注释有详细解说// TODO

六、协程上下文

Kotlin协程完全解析

上图紫色部分是完成了操作符的相关类

CoroutineContext是能够相加的,加完变成这种结构:

Kotlin协程完全解析

中心办法便是plusget,完成上下文的拼接,一起运用伴生目标完成去重

// 举个比如
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
↓
SupervisorJob().plus(Dispatchers.Main)
↓
Job.plus(ContinuationInterceptor)
↓
public operator fun plus(context: CoroutineContext): CoroutineContext =
    if (context === EmptyCoroutineContext) {
        // fast path -- avoid lambda creation
        // 假如要合并的是一个空上下文,直接回来当时的上下文
        this
    } else {
        // this -> Job, context -> ContinuationInterceptor
        context.fold(this) { acc, element ->
            // acc -> Job, context -> ContinuationInterceptor
            // 取出右侧的上下文的 key, acc.minusKey计算出左侧上下文除掉这个key后剩下的上下文内容
            val removed = acc.minusKey(element.key)
            // removed -> Job
            if (removed === EmptyCoroutineContext) {
                // acc 与 element 相等
                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)
                    } else {
                        CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }
        }
    }

恕我直言,这篇文章写的真好:Kotlin协程上下文CoroutineContext是如何可相加的,是时分锻炼下逻辑了 (:」∠) 我就不打开讲了。


以上,如有错漏敬请告知

Kotlin协程完全解析