Kotlin協程-一個協程的生命週期

《Kotlin協程》均基於Kotlinx-coroutines 1.3.70

在安卓或者kotlin平臺上使用協程是很簡單的一件事情。舉一個最簡單的例子,不依賴安卓平臺的協程代碼,

fun main() {
    GlobalScope.launch {
        delay(1000L) // 非阻塞的等待 1 秒鐘(默認時間單位是毫秒)
        println("World!") // 在延遲後打印輸出
    }
    delay(100L)
    println("Hello,") // 協程已在等待時主線程還在繼續

    Thread.sleep(2000L) // 阻塞主線程 2 秒鐘來保證 JVM 存活
    println("out launch done")
}

這裏啓動了一個常見的coroutine,GlobalScope.launch啓動的協程在協程上下文中會派發給底層的線程池去執行。它會經歷創建->攔截->暫停->resume->暫停->resume—>完成的生命週期。

Created with Raphaël 2.2.0創建暫停繼續完成?結束yesno

協程的生命週期是在一系列的邏輯中實現的,背後是 Context-Dispatcher-Scheduler 的支持。這些代碼沒有很深的技術,用的都是常見的軟件設計思想,梳理這部分邏輯大概需要兩天時間,過程中主要需要保持兩條清晰的線索在腦裏,一個是協程的生命週期,一個是生命週期背後支撐的邏輯概念。

創建協程

launch/async
協程的創建有兩個常用接口launch和async,兩個接口的內部實現基本一致。以launch來說,它的源碼在 Builders.common.kt

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,//默認的立即啓動方式
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)//創建context
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)//創建立即啓動的coroutine
    coroutine.start(start, coroutine, block)
    return coroutine
}

launch會返回一個Job對象,Job提供了一種類似Future的實現,可以在協程運行完成後返回結果。

返回coroutine之前會調用 coroutine.start()方法,

coroutine.start(start, coroutine, block)

這行代碼作用是把協程加入到任務隊列。代碼調用的是 AbstractCoroutine.kt的 start方法

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

從start(block, receiver, this)開始是派發到任務隊列的流程。此時的coroutine已經擁有了協程上下文,和默認的派發器和調度器。

CoroutineStart是一個枚舉類。start幹了啥?爲什麼一個枚舉類的值可以直接當函數使用?
這是因爲它使用了kotlin的語言特性–操作符重載,CoroutineStart枚舉類的invoke方法被重載了,所以可以直接用 start 去執行代碼。操作符重載的代碼在 CoroutineStart 中。所以上面的start()實際走的是下面的這段代碼。

@InternalCoroutinesApi
public operator fun <T> invoke(block: suspend () -> T, completion: Continuation<T>) =
    when (this) {
        CoroutineStart.DEFAULT -> block.startCoroutineCancellable(completion) //start實際執行的是這行代碼
        CoroutineStart.ATOMIC -> block.startCoroutine(completion)
        CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(completion)
        CoroutineStart.LAZY -> Unit // will start lazily
    }

派發

經過創建的協程就進入了派發流程,Dispatcher會將它依據規則加入到對應隊列裏。關於Dispatcher等一下會再說它是在什麼時候創建的什麼東西,這裏先記住有個Dispatcher就行。

block.startCoroutineCancellable(completion)

從上面這行代碼的startCoroutineCancellable跟進去來到 Cancellable.kt,它的代碼很簡單。

@InternalCoroutinesApi
public fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>) = runSafely(completion) {
    createCoroutineUnintercepted(completion).intercepted().resumeCancellableWith(Result.success(Unit))
}

createCoroutineUnintercepted是一個expect函數,意味着它會有一個actual的實現。但我們在kotlinx的代碼中是找不到actual實現的,它的actual實現在Kotlin中,後面我們會分析這塊代碼。

現在只要記住createCoroutineUnintercepted,最終會調用下面這個create接口就行
create

上面的代碼哪裏來的?

我們寫的協程代碼,會經過kotlinc的編譯,而這些代碼就是在編譯期插入的。

createCoroutineUnintercepted調用了create接口後,會得到一個 Continuation 的實現。在開篇說過,Continuation是一個帶resumeWith()的接口,

public interface Continuation<in T> {
    public val context: CoroutineContext
    public fun resumeWith(result: Result<T>)
}

這裏create之後返回的,實際是個ContinuationImpl實現。代碼在 ContinuationImpl.kt中,ContinuationImpl比較特殊,它不在kotlinx項目裏,而在kotlin-stdlib標準庫。

kotlin的協程架構着實有點蛋疼,這種有些在標準庫,有些在kotlinx裏的方式讓人捉摸不透。

@SinceKotlin("1.3")
// State machines for named suspend functions extend from this class
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 }

上面省略了一些代碼。得到了ContinuationImpl實現後會接着調用Cancellable中的下一個方法,intercepted(),也就是上面的代碼。重點是這行,

context[ContinuationInterceptor]

這裏會拿到當前上下文Context中的派發器對象,默認的實現是CoroutineDispatcher。這個東西是哪裏來的,回到最上面的 launch接口,第一行是 newCoroutineContext,在創建context的邏輯裏會順便創建派發器。

接着在 CoroutineDispatcher 中,會調用 interceptContinuation() 方法返回一個DispatchedContinuation對象。

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

顧名思義,DispatchedContinuation代表現在的coroutine,不僅實現了continuation接口,同時還通過代理的方式持有了Dispatcher。

再接着看Cancellable。intercept之後,我們的協程就處於攔截/暫停/掛起狀態,在協程裏的概念叫suspend。接着執行Cancelable的下一個方法resumeWIth(),調用棧最後會走到resumeCancellableWith()。

目前的corutine是 DispatchedContinuation,resumeCancellableWith的實現在它的代碼中,

inline fun resumeCancellableWith(result: Result<T>) {
    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)
            }
        }
    }
}

裏面的dispatcher,是在創建的時候傳入的 Dispatcher實現。這裏通過代理模式,調用它的派發函數。之後就進入了真正的派發入隊流程。

kotlin協程的常用派發器有兩個,EventLoop和DefaultScheduler,關於EventLoop我們後面會講,它比較特殊,它的設計是爲了阻塞當前線程,完成一系列coroutine。

DefaultScheduler的實現在 Dispatcher.kt。相關的類還有 Dispatchers.common.kt,Dispatchers.kt。他們之間的關係是
Dispatches.common.kt是公用類,它指導了所有平臺的協程需要實現的公共接口。而不同的平臺,比如jvm,js,native,他們的具體實現都叫Disaptchers.kt,分別放在不同的包下面。

Dispatches(多了個s)定義了幾種派發類型,之前說過,Default,MAIN,Unconfine,IO。我們關注的是Default,其他三個的邏輯可以參考Default的實現。

Dispatcher的創建時機在 newCoroutineContext(),也就是launch的第一行。它的實現在 CoroutineContext.kt裏(jvm包下),

internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
    if (useCoroutinesScheduler) DefaultScheduler else CommonPool

/**
 * Creates context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher nor
 * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on).
 *
 * See [DEBUG_PROPERTY_NAME] for description of debugging facilities on JVM.
 */
@ExperimentalCoroutinesApi
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext):  CoroutineContext { //創建context
    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
}

創建context的時候會用到 Dispatchers.Default,最終它會回去調用上面那句createDefaultDispatcher()。從而拿到 DefaultScheduler 單例。

jvm平臺的Dispatcher.Default是這樣的

public actual val Default: CoroutineDispatcher = createDefaultDispatcher()

createDefaultDispatcher()的實現剛剛上面介紹了。

然後進去Dispatcher看,在CoroutineContinuation調用了disaptcher.dispatch(),調用的是哪個函數。

override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
    try {
        coroutineScheduler.dispatch(block)
    } catch (e: RejectedExecutionException) {
        DefaultExecutor.dispatch(context, block)
    }

coroutineScheduler就是下面要說到的調度器了。
現在coroutine還處於suspend狀態,接下來就要進入調度邏輯了。

調度

默認的調度實現是 CoroutineScheduler,在CoroutineScheduler.kt下。它的diaptch()函數,

fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
    trackTask() // this is needed for virtual time support
    val task = createTask(block, taskContext) //封裝任務
    // try to submit the task to the local queue and act depending on the result
    val currentWorker = currentWorker() //獲取當前線程
    val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch) //加入worker本地執行隊列
    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
    // Checking 'task' instead of 'notAdded' is completely okay
    if (task.mode == TASK_NON_BLOCKING) {
        if (skipUnpark) return
        signalCpuWork() //執行CPU密集型協程
    } else {
        // Increment blocking tasks anyway
        signalBlockingWork(skipUnpark = skipUnpark) //執行阻塞型協程
    }
}

在調度器裏面有兩個新的概念,Worker和Queue。所謂Worker其實就是Thread,跟java的Thread是同一個東西。Queue是任務隊列,它又分兩種隊列,一個是Worker內部的localQueue,一個是Scheduler裏的globalQueue。雖然 globalQueue 又分 blocking 和 cpu,但這裏可以簡單理解爲 globalQueue裏面放的是阻塞型IO任務。

回到Worker,它有個內部成員 localQueue,

internal inner class Worker private constructor() : Thread() {
    init {
        isDaemon = true
    }

    // guarded by scheduler lock, index in workers array, 0 when not in array (terminated)
    @Volatile // volatile for push/pop operation into parkedWorkersStack
    var indexInArray = 0
        set(index) {
            name = "$schedulerName-worker-${if (index == 0) "TERMINATED" else index.toString()}"
            field = index
        }

    constructor(index: Int) : this() {
        indexInArray = index
    }

    inline val scheduler get() = this@CoroutineScheduler

    @JvmField
    val localQueue: WorkQueue = WorkQueue() //本地隊列

localQueue是存在於每個worker的,也就是說,不管開了多少個線程,每個線程都持有一個屬於自己的隊列。Worker在創建完畢之後就進入運行狀態,直到它的狀態被設置爲銷燬爲止。

private fun createNewWorker(): Int {
    synchronized(workers) {
        ...
        val worker = Worker(newIndex)
        workers[newIndex] = worker
        require(newIndex == incrementCreatedWorkers())
        worker.start()
        return cpuWorkers + 1
    }
}

省略了部分代碼。在創建完worker之後,worker對象會加入到一個數組裏,這個數組是調度器CoroutineScheduler的成員。之後就會調用start()方法了。worker會看是否有可以執行的任務,有的話就取出來做,沒有的話就進入park狀態。park是線程調度裏一個不是很常見的概念,這部分可以再仔細研究。

下面是執行部分的邏輯。

執行

在Worker的run()函數會調用runWorker()函數,

private fun runWorker() {
    var rescanned = false
    while (!isTerminated && state != WorkerState.TERMINATED) {
        val task = findTask(mayHaveLocalTasks)
        // Task found. Execute and repeat
        if (task != null) {
            rescanned = false
            minDelayUntilStealableTaskNs = 0L
            executeTask(task) //執行

跳到 executeTask(),

private fun executeTask(task: Task) {
    val taskMode = task.mode
    idleReset(taskMode)
    beforeTask(taskMode)
    runSafely(task)
    afterTask(taskMode)
}

idleReset,beforeTask和afterTask做的是一些狀態設置和回調。主要的執行是 runSafely(),

fun runSafely(task: Task) {
    try {
        task.run() //真正的執行
    } catch (e: Throwable) {
        val thread = Thread.currentThread()
        thread.uncaughtExceptionHandler.uncaughtException(thread, e)
    } finally {
        unTrackTask()
    }
}

task是個啥?之前在intercept()返回的DispatchedContinuation,它繼承了 DispatchedTask(),這裏的task就是它了。在 DispatchedTask.kt裏,

internal abstract class DispatchedTask<in T>(
    @JvmField public var resumeMode: Int
) : SchedulerTask() {
    internal abstract val delegate: Continuation<T>

    internal abstract fun takeState(): Any?

    internal open fun cancelResult(state: Any?, cause: Throwable) {}

    @Suppress("UNCHECKED_CAST")
    internal open fun <T> getSuccessfulResult(state: Any?): T =
        state as T

    internal fun getExceptionalResult(state: Any?): Throwable? =
        (state as? CompletedExceptionally)?.cause

    public final override fun run() {
        val taskContext = this.taskContext
        var fatalException: Throwable? = null
        try {
            val delegate = delegate as DispatchedContinuation<T>
            val continuation = delegate.continuation
            val context = continuation.context
            val state = takeState() // NOTE: Must take state in any case, even if cancelled
            withCoroutineContext(context, delegate.countOrElement) {
                val exception = getExceptionalResult(state)
                val job = if (resumeMode.isCancellableMode) context[Job] else null
                /*
                 * Check whether continuation was originally resumed with an exception.
                 * If so, it dominates cancellation, otherwise the original exception
                 * will be silently lost.
                 */
                if (exception == null && job != null && !job.isActive) {
                    val cause = job.getCancellationException()
                    cancelResult(state, cause)
                    continuation.resumeWithStackTrace(cause)
                } else {
                    if (exception != null) continuation.resumeWithException(exception)
                    else continuation.resume(getSuccessfulResult(state)) //調用continuation的resume
                }
            }

最後一行是調用continuation的地方。這裏的continuation又是在最開始創建DispatchedContinuation那裏傳進來的。它實際是個 BaseContinuationImpl 對象,

internal abstract class BaseContinuationImpl(
    // This is `public val` so that it is private on JVM and cannot be modified by untrusted code, yet
    // it has a public getter (since even untrusted code is allowed to inspect its call stack).
    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
        var current = this
        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) {
                val completion = completion!! // fail fast when trying to resume continuation without 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() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    // top-level completion reached -- invoke and return
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }

上面的 invokeSuspend()纔是真正調用我們寫的協程的地方。到這裏就是真正的執行流程了。

整個流程下來非常繞,有些代碼在標準庫,而有些又在協程庫,山路十八彎。

invokeSuspend()是在編譯期插入的,比如下面這段代碼

fun main() {
    GlobalScope.launch {
        println("Hello!")
        delay(100L) // 非阻塞的等待 1 秒鐘(默認時間單位是毫秒)
        println("World!") // 在延遲後打印輸出
    }
    Thread.sleep(400L) // 阻塞主線程 2 秒鐘來保證 JVM 存活
    println("out launch done")
}

非常簡單,只起了一個協程的情況。在編譯後會變成下面這樣
invoke

它實際是個狀態機,每次掛起和resume都會發生狀態切換,根據狀態執行不同的case。

結束

協程結束的時機是在coroutine返回的不是 COROUTINE_SUSPENDED 的時候。
invokeSuspend的case中,遇到掛起函數會返回COROUTINE_SUSPENDED,而在ContinuationImpl中收到它則直接返回。

    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
        var current = this
        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) {
                val completion = completion!! // fail fast when trying to resume continuation without 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() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    // top-level completion reached -- invoke and return
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }

在這裏插入圖片描述

所以當最後一個case的時候,返回的是Unit.INSTANCE。此時協程就真正的地執行完畢了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章