kotlin 协程是怎么启动的

985 阅读5分钟

屏幕截图 2024-05-25 101240.png

在上一篇文章 kotlin 协程入门教程 中,讲过协程本质是线程池的Task。本篇文章就从源码的角度,来看看协程任务是怎么一步一步被启动的。

协程启动流程

如果你只对协程启动流程感兴趣,但不想看源码分析,直接看下面的kotlin协程启动的流程图就好。如果对源码分析感兴趣,可以继续往下看源码分析的部分。

kotlin协程 (1).jpg

源码分析

屏幕截图 2024-05-25 002850.png

首先我们先看一下 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) else
        StandaloneCoroutine(newContext, active = true) //注释1
    coroutine.start(start, coroutine, block)
    return coroutine
}

CoroutineStart 是指协程的启动选项,默认是 CoroutineStart.DEFAULT。即默认情况下,代码会走到注释1的位置,然后会调用 StandaloneCoroutinestart 方法。

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

StandaloneCoroutine 是继承 AbstractCoroutine 的,调到的是它的 start 方法。源码如下:

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

到这里还没有结束,AbstractCoroutine 的 start 方法内部又调到了 CoroutineStartinvoke 方法。这里的 start(block, receiver, this) 其实等同于 start.invoke(block, receiver, this),这是kotlin中的invoke 约定。

@InternalCoroutinesApi
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
    }

在 invoke 方法中,我们能看到最后是调到了 startCoroutineCancellable 方法。它其实是挂起函数的的扩展函数。函数源码如下所示:

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
    receiver: R, completion: Continuation<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
) =
    runSafely(completion) {
        createCoroutineUnintercepted(receiver, completion) //注释1
        .intercepted() //注释2
        .resumeCancellableWith(Result.success(Unit), onCancellation) //注释3
    }

首先是注释1createCoroutineUnintercepted 方法,从它的方法名可以看出它会创建协程实例。不过你看它的方法原型,可以看到它返回了 Continuation 对象。那 Continuation 是什么东西呢? Continuation 是挂起函数实现挂起和恢复功能的核心接口,后面的文章介绍挂起函数时,再详细介绍。这里你只需要把 Continuation 理解成一个携带逻辑的callback就可以了。

launch {
   doSomething()
}
//等价于
val callback = Callback {
    doSomething()
}

注释2intercepted 方法则是找到 CoroutineContext 中的 ContinuationInterceptor,并调用它的 intercepted 方法。如果是是第一次调用,则会调用它的 interceptContinuation 方法。

public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
    (this as? ContinuationImpl)?.intercepted() ?: this
    
// ContinuationImpl 的 intercepted 方法   
public fun intercepted(): Continuation<Any?> =
    intercepted
        ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
            .also { intercepted = it }

CoroutineDispatcher 实现了 ContinuationInterceptor 的接口,之前在kotlin 协程入门教程中介绍过,CoroutineDispatcher 的功能是将协程任务分发到要求的线程上。其中它的 interceptContinuation 方法就是返回一个 DispatchedContinuation 对象。

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

如上图所示,DispatchedContinuation 的本质其实就是一个 Runnable。在创建 DispatchedContinuation 时会传入continuation 和 this, 其中 this 是指 CoroutineDispatcher

CoroutineDispatcher有四个重要的子类,分别是 HandlerContextDefaultIoSchedulerDefaultSchedulerUnconfined 。其中 HandlerContext 对应 Dispatchers.Main;DefaultIoScheduler 对应 Dispatchers.IO ; DefaultScheduler 对应于 Dispatchers.Default;Unconfined 对应于 Dispatchers.Unconfined。

从源码中可以看到,在注释1注释2的方法中,我们都是在做准备工作。那么启动协程的核心就在注释3的方法中了。在看注释3处的方法之前,我们需要先看一下 CoroutineContext 是什么时候被创建的?

还记得在kotlin 协程入门教程中讲过:所有协程都需要通过 CoroutineScope 来启动。其实,CoroutineContext 和 ContinuationInterceptor 的创建时机就在这里。下面我们通过源码来证明。

public fun CoroutineScope.launch(
    //当我们没有传入 CoroutineContext 时,会使用 EmptyCoroutineContext 作为默认传入值
    context: CoroutineContext = EmptyCoroutineContext, 
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    //这里会根据旧的 CoroutineContext,来创建新的CoroutineContext
    val newContext = newCoroutineContext(context) 
    ...
}

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    // newCoroutineContext 是 CoroutineScope 的扩展方法,其中的
    // coroutineContext 就是 CoroutineScope 的属性
    val combined = foldCopies(coroutineContext, context, true)
    ...
}

//这里以最常用的 lifecycleScope 来看看它内部是怎么创建 CoroutineContext 的
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope // 返回了 coroutineScope,继续往下看
    
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            ...
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                // 核心代码在这里
                SupervisorJob() + Dispatchers.Main.immediate 
            )
            ...
        }
    }

说回注释3resumeCancellableWith 方法。上面提到过 intercepted 返回一个 DispatchedContinuation 对象,因此会调用到 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)
}

DispatchedContinuation 中,resumeCancellableWith 会根据 dispatcher.isDispatchNeeded 来判断是否是否分发(dispatch)协程。通过上面的分析,我们知道 dispatcher 其实是 CoroutineDispatcher 的子类,默认情况下 CoroutineDispatcherisDispatchNeeded 只返回 true。当 dispatcher 为 Unconfined ; 或者 dispatcher 为 HandlerContext ,同时创建的和执行的线程的 looper 不一致时,才会返回 false。

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

当 dispatcher 为 HandlerContext 时,最后会执行 hander.post,这个就不多介绍了。当 dispatcher 为 Unconfined 时,走到第二个条件语句,这时协程的执行不会分发到其他的线程,只在当前线程执行。当 dispatcher 为 DefaultIoScheduler 或者 DefaultScheduler 时,最后执行的都是 CoroutineScheduler 的 dispatch 方法。源码如下:

fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
    ...
    //实际上是创建一个 TaskImpl 对象
    val task = createTask(block, taskContext)
    //获取当前 Worker,Worker 继承 Thread,本质是一个线程
    val currentWorker = currentWorker()
    //将Task添加到 Worker 线程本地队列
    val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch)
    ...
}

internal class TaskImpl(
    @JvmField val block: Runnable,
    submissionTime: Long,
    taskContext: TaskContext
) : Task(submissionTime, taskContext) {
    override fun run() {
        try {
            //这里实际上是执行 DispatchedContinuation 的 run 方法
            block.run()
        } finally {
            taskContext.afterTask()
        }
    }
}


internal inner class Worker private constructor() : Thread() {
    
    override fun run() = runWorker()

    private fun runWorker() {
        var rescanned = false
        while (!isTerminated && state != WorkerState.TERMINATED) {
            val task = findTask(mayHaveLocalTasks)
            if (task != null) {
                rescanned = false
                minDelayUntilStealableTaskNs = 0L
                //执行任务
                executeTask(task)
                continue
            } else {
                mayHaveLocalTasks = false
            }
            ...
    }
    
    private fun executeTask(task: Task) {
        ..
        runSafely(task)
        ...
     }
     
     fun runSafely(task: Task) {
        try {
            //最终是调用 task 的run 方法
            task.run()
        } catch (e: Throwable) {
            val thread = Thread.currentThread()
            thread.uncaughtExceptionHandler.uncaughtException(thread, e)
        } finally {
            unTrackTask()
        }
    }

}

dispatch 方法首先会创建一个 TaskImpl 对象,它内部的 run 方法会调用 DispatchedContinuation 的 run 方法。第二步则会获取 Worker,它继承 Thread,本质是一个线程。第三步则将创建的 Task 添加到 Worker 线程本地队列中去。

Worker 线程会不断从线程本地队列中获取 Task,经过 executeTask -> runSafely 方法,最后会调用 Task 的 run方法,最后调用到 DispatchedContinuation 的 run 方法,这时就完成了协程的启动。