Take a look,從delay()方法看協程的掛起與恢復

語言: CN / TW / HK

highlight: vs theme: smartblue


攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第4天,點擊查看活動詳情

本篇文章主要是講解協程相關的知識點,並以delay()方法簡單分析協程如何實現的掛起以及恢復

協程實現的原理就是CPS+狀態機,掘金很多優秀的文章都都寫的很好,我也寫不出太過於原理性的文章。本篇文章以一個掛起函數delay()舉例,來簡單講解協程如何實現的掛起與恢復,以及為什麼delay()不會阻塞主線程。

delay()的掛起與恢復

我們以下面的代碼舉例:

kotlin lifecycleScope.launch { delay(1000) val a = 10 val b = 50 println(a + b) }

kotlin編譯器會將上面的代碼翻譯下面代碼(非常簡陋的偽代碼):

```kotlin private fun test(completion: Continuation) { class StateMachine(completion) { val tag = 0 override fun resumeWith() { return test(this) } }

val machine = completion as? StateMachine ?: StateMachine(completion)

when (machine.tag) {
    0 -> {
       //1.
        //當delay方法執行完畢,就會調用machine的resumeWith()方法恢復協程執行
        machine.tag = 1
        delay(1000, machine)
    }
    1 -> {
       //2.
        val a = 10
        val b = 50
        println(a + b)
    }
}

} ```

掛起

創建一個狀態機對象StateMachine,第一次狀態機tag等於0,就會執行協程的掛起方法delay,並傳入狀態機實例machine,當delay方法執行完畢後,就會調用傳入的machineresumeWith方法恢復之後代碼的執行。

delay方法是如何執行的呢?為什麼不會阻塞主線程?我們探究其實現原理:

kotlin public suspend fun delay(timeMillis: Long) { return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> -> cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont) } }
繼續看下HandlerContextscheduleResumeAfterDelay方法(這裏以HandlerContext類舉例):

kotlin override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) { val block = Runnable { with(continuation) { resumeUndispatched(Unit) } } handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY)) } 可以看到,這就是藉助了Handler發送了一個延時執行的Message消息,所以調用delay()方法自然不會阻塞主線程的執行了。

恢復

重點scheduleResumeAfterDelay這段代碼:

kotlin val block = Runnable { with(continuation) { resumeUndispatched(Unit) } }

當上面的Message被執行後,就會調用傳入的continuation恢復協程的執行,可以把這個continuation理解為我們上面創建的machine對象,調用的方法resumeUndispatched是為resumeWith()方法。

當協程的掛起方法delay執行完畢後,就會走到狀態機StateMachine的方法resumeWith中,此時的tag已經變為1了,所以當再次調用test(Continuation)方法,並且走when的第二個分支,執行下面的代碼塊邏輯:

1 -> { //2. val a = 10 val b = 50 println(a + b) }

`

筆者這裏主要是簡單的描述了下協程的掛起與恢復,偽代碼寫的也非常簡陋,主要是方便大家理解,真實的情況請大家參考協程官方的源碼,有什麼疑問可以評論區一起交流。

總結

本篇文章主要是以delay()方法作為一個入口,看下協程如何掛起與恢復的,並沒有多麼神祕,接下來準備繼續寫一系列協程相關的文章,如果您感覺文章寫的還行,可以給個贊支持下,感謝!!

參考文章

揭祕kotlin協程的實現原理

這是我看過的講解協程的最好的一篇文章,大概有幾萬字,我看了大概5-6個小時,非常值得大家細細品讀。