Take a look,从delay()方法看协程的挂起与恢复

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

本篇文章主要是讲解协程相关的知识点,并以delay()方法简单分析协程如何实现的挂起以及恢复

协程实现的原理就是CPS+状态机,掘金很多优秀的文章都都写的很好,我也写不出太过于原理性的文章。本篇文章以一个挂起函数delay()举例,来简单讲解协程如何实现的挂起与恢复,以及为什么delay()不会阻塞主线程。

delay()的挂起与恢复

我们以下面的代码举例:

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

kotlin编译器会将上面的代码翻译下面代码(非常简陋的伪代码):

private fun test(completion: Continuation<Unit>) {
    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方法是如何执行的呢?为什么不会阻塞主线程?我们探究其实现原理:

public suspend fun delay(timeMillis: Long) {
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
          cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
    }
}

继续看下HandlerContextscheduleResumeAfterDelay方法(这里以HandlerContext类举例):

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这段代码:

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个小时,非常值得大家细细品读。

猜你喜欢

转载自juejin.im/post/7127433590677176351