kotlin.coroutines core 分析

kotlin.coroutines 是一套标准库之外的以生产为目的的框架,提供了丰富的 API 让我们可以进行异步操作。虽然这些功能你都可以通过 Kotlin Coroutine 标准库自己来实现。
Kotlin Coroutine suspend 原理解析
Kotlin Coroutine 标准库源码解析

一. CoroutineScope 接口

为何先从 CoroutineScope 接口讲起呢?
因为开启新协程的方法 launchasync 都是 CoroutineScope 扩展方法,必须通过 CoroutineScope 实例才能调用。

Kotlin Coroutine suspend 原理解析那一章中,我们知道可以通过 createCoroutine 创建协程,并调用协程的 resumeWith 来启动它;或者通过 startCoroutine 方法创建并启动协程。
kotlin.coroutines 框架下面,我们不需要调用这么底层的方法来启动协程,而是提供了 launchasync 来启动新协程,其实它们最后也是会调用底层的方法来启动协程。

1.1 协程作用域

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

可以看出 CoroutineScope 接口非常简单,只有一个成员属性,协程上下文 CoroutineContext

这个 CoroutineContext 属性很重要,CoroutineScope 被称为协程作用域,就是通过 CoroutineContext 来实现的。

作用域主要是用来复合协程之间的关系的。针对下面情况:

    GlobalScope.launch {
        launch {
        }
        launch {
            launch {
            }
        }
    }

这里创建了四个协程,它们之间关系是什么样子的。
当在协程作用域里面,通过 launch 方法创建新协程之后,新协程作用域被称为子协程,当前协程被称为新协程的父协程。它们满足下面规则:

  1. 当父协程被取消,则所有子协程也跟着都会被取消。
  2. 父协程需要等待子协程执行完毕之后才会最终进入完成状态,不管父协程自身的协程体是否已经执行完成。
  3. 子协程会继承父协程的协程上下文集合中的元素,如果子协程有相同 KEY 的元素,那么会覆盖父协程对应元素。

1.2 newCoroutineContext 方法

为新协程创建上下文 CoroutineContext 对象,它是 CoroutineScope 的扩展方法。

@ExperimentalCoroutinesApi
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    val combined = coroutineContext + context
    val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
    return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
        debug + Dispatchers.Default else debug
}

主要流程:

  1. 将当前协程作用域中的协程上下文对象 coroutineContext 与方法参数中的上下文对象 context 合并相加。
  2. 如果是 debug 模式,在上下文对象中添加 CoroutineId 元素
  3. 如果合并之后的上下文对象 combined 中没有协程拦截器 ContinuationInterceptor 元素,那么就将 Dispatchers.Default 元素添加到上下文集合中。

1.3 launch 方法

创建新协程,返回Job 对象实例;它也是 CoroutineScope 的扩展方法

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

它有三个参数:

  1. context : 额外添加的协程上下文元素集合,默认是空元素集合 EmptyCoroutineContext
  2. start : 新协程的启动模式;有四种模式 DEFAULTLAZYATOMICUNDISPATCHED
  3. block : 协程体,新协程的执行代码

方法主要流程:

  1. 通过 newCoroutineContext 方法为新协程创建新的协程上下文对象 newContext
  2. 根据启动模式的不同,创建 StandaloneCoroutine 或者 LazyStandaloneCoroutine 协程对象 coroutine

LazyStandaloneCoroutineStandaloneCoroutine 的子类

  1. 调用 coroutinestart 方法,并返回这个对象,它也是 Job 的子类。

通过返回的这个 Job 对象,可以调用它的 join 方法等待协程完成,或者调用 cancel 方法取消协程。

1.4 async 方法

也是创建新协程,但是它返回 Deferred 对象,它是 Job 的子类,但是它可以返回结果值。相当于 java 中的 RunnableCallable 的区别。

async 方法与 launch 方法相比,它可以得到协程执行之后的结果值。

public fun  CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

方法参数和 launch 方法几乎一样,只不过它的协程体 block 方法有返回值。
方法流程也和 launch 方法几乎一样,只不过创建了 DeferredCoroutine 或者 LazyDeferredCoroutine 对象的协程。

二. AbstractCoroutine 类

在上一节中,launchasync 方法返回的 StandaloneCoroutineDeferredCoroutine, 都有一个共同父类 AbstractCoroutine

2.1 成员属性

public abstract class AbstractCoroutine(
    /**
     * The context of the parent coroutine.
     */
    @JvmField
    protected val parentContext: CoroutineContext,
    active: Boolean = true
) : JobSupport(active), Job, Continuation, CoroutineScope {
          @Suppress("LeakingThis")
    public final override val context: CoroutineContext = parentContext + this

    /**
     * The context of this scope which is the same as the [context] of this coroutine.
     */
    public override val coroutineContext: CoroutineContext get() = context

    override val isActive: Boolean get() = super.isActive
   ....
}

AbstractCoroutine 的继承关系中可以看出:

  1. 它是 Job 的子类,说明它是一个任务,可以调用 Jobcancel 方法取消它,或者调用 join 方法等待这个任务完成。
  2. 它是 Continuation 子类,说明它是一个挂起点,可以调用 resumeWith 方法恢复。
  3. 它是 CoroutineScope 子类,说明它也是一个协程作用域。

重要的成员属性:

  1. parentContext : 表示父协程的上下文。

这个属性非常重要,它里面有父协程的任务 Job ,用来实现父协程任务取消时,子协程联动取消;或者子协程发生异常的时候,取消父协程的任务。

  1. active : 表示当前 AbstractCoroutine 代表的任务,创建时是处于活跃状态,还是不活跃的新建状态。

注:它不是 AbstractCoroutine 的成员属性,只是创建时传递的参数,因为它没有使用 val 或者 var 修饰。

  1. context : 当前 AbstractCoroutine 协程的协程上下文。parentContext + this; 因为 AbstractCoroutineJob 的子类,所以它会替换父协程上下文 parentContext 集合中的 Job 元素。

contextparentContext 区别就是,两个上下文集合中 Job 元素不同。 parentContext 是父协程任务 Jobcontext 是本协程(子协程) 的任务 Job

  1. coroutineContext :协程作用域的协程上下文,这里与 context 相同。
  2. isActive :表示当前任务 Job 是否处于活跃状态。

2.2 重要方法

2.2.1 start 方法

    public fun start(start: CoroutineStart, block: suspend () -> T) {
        initParentJob()
        start(block, this)
    }

    public fun  start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
        initParentJob()
        start(block, receiver, this)
    }

launchasync 方法流程中,在创建完 AbstractCoroutine 子类对象后,都会调用它的 start 方法。
方法流程:先调用 initParentJob 方法,将父协程任务 Job 与本协程联系起来;再调用 CoroutineStartinvoke 方法运行协程体 block

2.2.2 initParentJob 方法

    internal fun initParentJob() {
        initParentJobInternal(parentContext[Job])
    }

    // 在 JobSupport 类中
    internal fun initParentJobInternal(parent: Job?) {
        assert { parentHandle == null }
        if (parent == null) {
            parentHandle = NonDisposableHandle
            return
        }
        parent.start() // make sure the parent is started
        @Suppress("DEPRECATION")
        val handle = parent.attachChild(this)
        parentHandle = handle
        // now check our state _after_ registering (see tryFinalizeSimpleState order of actions)
        if (isCompleted) {
            handle.dispose()
            parentHandle = NonDisposableHandle // release it just in case, to aid GC
        }
    }

可以看出 initParentJob 方法就是将父协程的 Job 与子协程联系起来。

2.3 重要子类

2.3.1 StandaloneCoroutine 类

private open class StandaloneCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine(parentContext, active) {
    override fun handleJobException(exception: Throwable): Boolean {
        handleCoroutineException(context, exception)
        return true
    }
}

launch 方法创建的默认 Job, 可以看出 StandaloneCoroutine 只是复写了处理任务异常 handleJobException 方法。

2.3.2 LazyStandaloneCoroutine 类

private class LazyStandaloneCoroutine(
    parentContext: CoroutineContext,
    block: suspend CoroutineScope.() -> Unit
) : StandaloneCoroutine(parentContext, active = false) {
    private val continuation = block.createCoroutineUnintercepted(this, this)

    override fun onStart() {
        continuation.startCoroutineCancellable(this)
    }
}

它是 StandaloneCoroutine 的子类,active = false 表示这个任务 Job 创建时不处于活跃状态。

  1. 通过 block.createCoroutineUnintercepted(this, this) 创建 suspendblock 方法对应的协程。

suspend 函数想要在非 suspend 方法中运行,只能通过 suspend 函数扩展方法 createCoroutineUnintercepted 创建协程,再调用它的 resumeWith 方法运行协程;我们在 Kotlin Coroutine suspend 原理解析有详细介绍。

  1. onStart 方法中,调用 continuation.startCoroutineCancellable(this) 方法,启动协程,即执行 block 函数。

onStartAbstractCoroutineonStartInternal 方法中调用,而 onStartInternal 方法只有当任务 Job 从不活跃状态转成活跃状态时候,才会调用一次。

也就是说 LazyStandaloneCoroutine 中的 block 函数,再刚创建的时候,并不会直接执行;只有当 LazyStandaloneCoroutine 对应任务从不活跃状态变成活跃状态才会执行。
任务 Job 从不活跃状态变成活跃状态,通过调用它的 jobstart 或者 join 方法。顺便说一句 Deferred 类的 await 方法也是可以的。

2.3.3 DeferredCoroutine 类

public interface Deferred : Job {

    public suspend fun await(): T

    public val onAwait: SelectClause1

    @ExperimentalCoroutinesApi
    public fun getCompleted(): T

    @ExperimentalCoroutinesApi
    public fun getCompletionExceptionOrNull(): Throwable?
}

private open class DeferredCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine(parentContext, active), Deferred, SelectClause1 {
    override fun getCompleted(): T = getCompletedInternal() as T
    override suspend fun await(): T = awaitInternal() as T
    override val onAwait: SelectClause1 get() = this
    override fun  registerSelectClause1(select: SelectInstance, block: suspend (T) -> R) =
        registerSelectClause1Internal(select, block)
}

可以看出 DeferredJob 的子类,它可以获取任务执行后的结果值。
DeferredCoroutine 也是继承 AbstractCoroutine 类,但是可以通过 await 或者 getCompleted 获取结果值。

2.3.4 LazyDeferredCoroutine 类

private class LazyDeferredCoroutine(
    parentContext: CoroutineContext,
    block: suspend CoroutineScope.() -> T
) : DeferredCoroutine(parentContext, active = false) {
    private val continuation = block.createCoroutineUnintercepted(this, this)

    override fun onStart() {
        continuation.startCoroutineCancellable(this)
    }
}

这个几乎 LazyStandaloneCoroutine 实现流程一样。

三. CoroutineStart 类

AbstractCoroutine 类中,我们知道它的 start 方法最终会调用到 CoroutineStartinvoke 方法。

CoroutineStart 是一个枚举类型,有四个值,分别是 DEFAULTLAZYATOMICUNDISPATCHED ;它们之间的不同,通过分析 invoke 方法就明白了。

    @InternalCoroutinesApi
    public operator fun  invoke(block: suspend () -> T, completion: Continuation): Unit =
        when (this) {
            DEFAULT -> block.startCoroutineCancellable(completion)
            ATOMIC -> block.startCoroutine(completion)
            UNDISPATCHED -> block.startCoroutineUndispatched(completion)
            LAZY -> Unit // will start lazily
        }

    @InternalCoroutinesApi
    public operator fun  invoke(block: suspend R.() -> T, receiver: R, completion: Continuation): Unit =
        when (this) {
            DEFAULT -> block.startCoroutineCancellable(receiver, completion)
            ATOMIC -> block.startCoroutine(receiver, completion)
            UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
            LAZY -> Unit // will start lazily
        }

这两个方法流程一样,只不过下面那个方法多接受 receiver 参数。

3.1 DEFAULT

调用 startCoroutineCancellable 方法,在指定的拦截器线程中执行协程,也就是 block 函数;但是在运行 block 函数之前,用户是可以手动取消,不让执行的。

internal fun  (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation) =
    runSafely(completion) {
        createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit))
    }

startCoroutineCancellable 方法实现来看:

  1. runSafely 方法保证协程抛出未捕捉异常时,会回调 completion.resumeWith(Result.failure(e)) 通知失败。

  2. 通过 createCoroutineUnintercepted 方法,会创建 suspend (R) -> T 函数对应的协程 ContinuationImpl 对象。并将 completion 的上下文对象 context 赋值给 ContinuationImpl 对象的 context 。 这个具体请看Kotlin Coroutine suspend 原理解析

  3. 调用 intercepted() 方法,其实就是调用 ContinuationImpl 里的 intercepted 方法

    public fun intercepted(): Continuation =
        intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }

从协程上下文 context 获取拦截器 ContinuationInterceptor 对象,并调用它的 interceptContinuation 方法,获取被拦截器包装后的协程 Continuation 对象。

  1. 最后调用 resumeCancellableWith 方法,启动可取消的协程。

注意这里的协程,已经是包装后的协程 Continuation 对象,也就是说会经过拦截器的方法。一般拦截器都是实现线程的切换。

3.2 ATOMIC

调用 startCoroutine 方法,在指定的拦截器线程中直接执行协程,也就是 block 函数。

@SinceKotlin("1.3")
@Suppress("UNCHECKED_CAST")
public fun  (suspend R.() -> T).startCoroutine(
    receiver: R,
    completion: Continuation
) {
    createCoroutineUnintercepted(receiver, completion).intercepted().resume(Unit)
}

可以看出与 startCoroutineCancellable 方法大体相同。
通过 createCoroutineUnintercepted 方法创建 suspend R.() -> T 函数对应的协程体 ContinuationImpl 对象,再调用 intercepted 方法得到被拦截器封装的 Continuation 对象,最后调用 resume 直接执行协程,也就是 suspend R.() -> T 函数。

唯一的不同就是 resumeCancellableWith 方法,再还未执行协程,也就是运行 suspend R.() -> T 函数之前,是可以取消的。
resume 方法会直接执行协程,也就是运行 suspend R.() -> T 函数,直到遇到第一个挂起点。

3.3 UNDISPATCHED

调用 startCoroutineUndispatched 方法,直接在当前线程执行协程,也就是 block 函数。

internal fun  (suspend (R) -> T).startCoroutineUndispatched(receiver: R, completion: Continuation) {
    startDirect(completion) { actualCompletion ->
        withCoroutineContext(completion.context, null) {
            startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
        }
    }
}

这个方法与 startCoroutineCancellablestartCoroutine 差距很大。

其中最大的差距就是 startCoroutineCancellablestartCoroutine 都会调用协程的 intercepted() 包装一下,这样执行协程的时候,会经过协程拦截器,这样实现线程之间的切换。
但是这个方法没有调用,而是直接执行 startCoroutineUninterceptedOrReturn 方法

// 在 IntrinsicsJvm 类中,有具体实现
@SinceKotlin("1.3")
@InlineOnly
public actual inline fun  (suspend R.() -> T).startCoroutineUninterceptedOrReturn(
    receiver: R,
    completion: Continuation
): Any? = (this as Function2, Any?>).invoke(receiver, completion)

可以看出,通过invoke 方式,直接调用协程体(即 (suspend R.() -> T) 函数);所以调用的时候,是不经过协程拦截器的,也就是说直接在当前线程执行协程体。

注: 协程体根据内部挂起点分段执行的(挂起点就是调用 suspend 函数的地方), 而从挂起点返回后,协程体函数在那个线程执行,那这个就需要看具体实现了。

3.4 LAZY

 LAZY -> Unit // will start lazily

可以看出不做任何操作啊,而协程体 (即 suspend R.() -> T block函数)调用是通过 LazyStandaloneCoroutineLazyDeferredCoroutine 中实现的。

四. CoroutineDispatcher

协程分配器,协程之间的线程切换就是通过这个 CoroutineDispatcher 来实现的。

public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
        .......
}

它是协程拦截器 ContinuationInterceptor 的子类,通过 AbstractCoroutineContextElement(ContinuationInterceptor) 知道,默认情况下它的 Elementkey 就是 ContinuationInterceptor ,在协程上下文 context 元素集合中只会存在一个 ContinuationInterceptor 类型元素。

4.1 isDispatchNeeded 方法

协程是否需要进行调度。默认情况下都是返回 true

    public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true

如果返回 true ,就会调用 dispatch 方法;如果返回 false ,那么协程就在当前线程恢复。

4.2 dispatch 方法

    public abstract fun dispatch(context: CoroutineContext, block: Runnable)

是一个 abstract 方法,由子类来实现,进行线程之间切换的。

一般情况下,使用一个线程池,将 block 放在线程池中执行,这样就实现线程之间切换了。

4.3 interceptContinuation 方法

    public final override fun  interceptContinuation(continuation: Continuation): Continuation =
        DispatchedContinuation(this, continuation)

复写了协程拦截器 ContinuationInterceptor 中的方法。返回包装后的 DispatchedContinuation 协程对象。

    override fun resumeWith(result: Result) {
        val context = continuation.context
        val state = result.toState()
        if (dispatcher.isDispatchNeeded(context)) {
            _state = state
            resumeMode = MODE_ATOMIC_DEFAULT
            dispatcher.dispatch(context, this)
        } else {
            executeUnconfined(state, MODE_ATOMIC_DEFAULT) {
                withCoroutineContext(this.context, countOrElement) {
                    continuation.resumeWith(result)
                }
            }
        }
    }

   inline fun resumeCancellableWith(result: Result) {
        val state = result.toState()
        if (dispatcher.isDispatchNeeded(context)) {
            _state = state
            resumeMode = MODE_CANCELLABLE
            dispatcher.dispatch(context, this)
        } else {
            executeUnconfined(state, MODE_CANCELLABLE) {
                if (!resumeCancelled()) {
                    resumeUndispatchedWith(result)
                }
            }
        }
    }

它们是 DispatchedContinuationresume 方法实现。

通过 CoroutineDispatcherisDispatchNeeded 方法判断是否需要调度。如果返回 true ,则调用 dispatch 方法进行线程切换;负责通过 continuation.resumeWith(result) 方法直接在当前线程恢复协程。

五. withContext 方法

切换协程上下文环境。一般情况下都是 suspend 函数执行的线程。

public suspend fun  withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
        // compute new context
        val oldContext = uCont.context
        val newContext = oldContext + context
        // always check for cancellation of new context
        newContext.checkCompletion()
        // FAST PATH #1 -- new context is the same as the old one
        if (newContext === oldContext) {
            val coroutine = ScopeCoroutine(newContext, uCont)
            return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
        }
        // FAST PATH #2 -- the new dispatcher is the same as the old one (something else changed)
        // `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher)
        if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) {
            val coroutine = UndispatchedCoroutine(newContext, uCont)
            // There are changes in the context, so this thread needs to be updated
            withCoroutineContext(newContext, null) {
                return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
            }
        }
        // SLOW PATH -- use new dispatcher
        val coroutine = DispatchedCoroutine(newContext, uCont)
        coroutine.initParentJob()
        block.startCoroutineCancellable(coroutine, coroutine)
        coroutine.getResult()
    }
}

方法流程分为三个部分, 根据新旧协程上下文不同来区分:

  1. 新旧上下文一样, 即 newContext === oldContext ,说明没有任何改变,那么就在当前上下文环境,当前线程执行 blocksuspend 方法。
  2. 新旧上下文不一样,但是拥有的协程拦截器 ContinuationInterceptor 一样,也就是说执行线程是一样的,那么就在当前线程切换一下协程上下文 newContext 执行 blocksuspend 方法。
  3. 新旧上下文不一样, 拥有的协程拦截器 ContinuationInterceptor 也不一样;
    那么既要切换上下文环境也需要切换线程 执行 blocksuspend 方法。

这三种情况,返回的协程包装类也是不一样的,分别是 ScopeCoroutineUndispatchedCoroutineDispatchedCoroutine
其中 UndispatchedCoroutineDispatchedCoroutine 都是 ScopeCoroutine 子类。
blocksuspend 方法执行完成之后,都会调用 ScopeCoroutineUndispatchedCoroutineDispatchedCoroutineafterResume 方法。

针对 ScopeCoroutine

    override fun afterResume(state: Any?) {
        // Resume direct because scope is already in the correct context
        uCont.resumeWith(recoverResult(state, uCont))
    }

直接调用 resumeWith 方法恢复协程,不需要切换线程,也不需要切换上下文。

针对 UndispatchedCoroutine

   override fun afterResume(state: Any?) {
        // resume undispatched -- update context by stay on the same dispatcher
        val result = recoverResult(state, uCont)
        withCoroutineContext(uCont.context, null) {
            uCont.resumeWith(result)
        }
    }

可以看出要将协程上下文切换到旧的 uCont.context, 然后在调用 resumeWith 方法恢复协程。

针对 DispatchedCoroutine

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

要通过 uCont.intercepted() 方法,在 uCont 协程拦截器中恢复,也就是说将线程切换到协程调度线程中。

你可能感兴趣的:(kotlin.coroutines core 分析)