Kotlin协程核心原理终极理解

我们先来看一段代码,就拿最常见的网络请求为例子:

    fun uploadFile(···) {
        ···
        viewModelScope.launch {
            //标记1
            try {
                val bean = uploadFileApi.uploadFile(···)
            //标记2
                ···
            } catch (e: Exception) {
                ···
            }
        }
    }

首先问大家一个问题:标记1和标记2分别执行在哪个线程里面?

我们来查看viewModelScope源码:

   public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
   }

注意Dispatchers.Main,可见通过viewModelScope启动的协程是运行在Android主线程上的。

所以标记1和标记2都是运行在主线程里面的。

我们再来看uploadFile这个函数:

suspend fun uploadFile(···):返回值

我们使用了Retrofit请求,这又是一个suspend函数。那么第二个问题来了:suspend有什么用?

suspend能够触发挂起吗?

    private suspend fun test(){
        print("宇哥好帅")
    }

然后把这段代码放到标记1处:

fun uploadFile(···) {
        ···
        viewModelScope.launch {
            test()
            try {
                val bean = uploadFileApi.uploadFile(···)
            //标记2
                ···
            } catch (e: Exception) {
                ···
            }
        }
    }

看截图:

 你会发现这个suspend编译器提示我们是多余的,其实suspend函数本身是不会起到任何挂起的作用的,如果想要suspend函数起作用,那么一定是suspend函数内部调用了协程自带的挂起函数,比如说withContext等。suspend本身仅仅起到提示的作用,提示方法要进行耗时或IO操作。

再问一个问题:协程挂起的本质是什么?

很多博客文章说的云里雾里,其实就是三个字:切线程!

public final fun uploadFile(···): kotlin.Unit { /* compiled code */ }

我们去build里面看,uploadFile代码会变成这样,在方法内部,retrofit帮我们完成了切线程的操作。

再回到上面的test方法,我们改造一下:

 这个时候编译器就不会提示suspend多余啦!

我们继续深入:为什么一个挂起函数只能在协程里面或是另一个挂起函数里面声明?

挂起是为了什么?挂起无非就是切了一个线程去执行一些任务,但是挂起后是会恢复的。而与挂起对应的恢复是在协程里面实现的。所以挂起函数只能声明在协程里,或是另一个挂起函数里,向上追溯的话,最终还是在协程里。

好多文章再说,协程比线程更高效?

答案是并不会!就比如说网络请求,协程只是切一个线程去完成网络请求,网络请求主要是IO操作,就是会有一个线程去进行IO操作,完成后再切回主线程。只是对主线程不阻塞,并不是对IO线程不阻塞。

理解了这些,我们就来一起看看协程的核心原理吧。

要讲挂起恢复的原理,就不得不先说一下CPS(Continuation-Passing-Style, 续体传递风格)。

我们编译后的挂起函数都会多一个参数:

continuation: Continuation<T>

假设有这样一段代码:

val a = a()
val y = A(a) // 挂起点 #1
b()
val z = B(a, y) // 挂起点 #2
c(z)

A方法和B方法为挂起方法。编译后字节码通过工具查看:

// 状态机当前状态
    int label = 0
     
    // 协程的局部变量
    A a = null
    Y y = null
     
    void resumeWith(Object result) {
        if (label == 0) goto L0
        if (label == 1) goto L1
        if (label == 2) goto L2
        else throw IllegalStateException()
         
      L0:
        // 这次调用,result 应该为空
        a = a()
        label = 1
        result = A(a)
        if (result == COROUTINE_SUSPENDED) return // 如果挂起了执行则返回
      L1:
        y = (Y) result
        b()
        label = 2
        result = B(a, y)
        if (result == COROUTINE_SUSPENDED) return // 如果挂起了执行则返回
      L2:
        Z z = (Z) result
        c(z)
        label = -1 // 没有其他步骤了
        return
    }         

注意:COROUTINE_SUSPEND表示协程被挂起,挂起函数执行结束后,状态会通过CAS更改为RESUMED。每次挂起函数执行结束后都会调用Continuation的resumeWith方法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Kotlin程是一种轻量级的并发框架,它可以在不创建新线程的情况下实现异步操作。Kotlin程的实现原理是基于挂起函数和Continuation(程上下文)。 在Kotlin程中,挂起函数是指可以被暂停执行,并在稍后继续执行的函数。在挂起函数中,可以使用`suspend`关键字来标记该函数为挂起函数。当调用一个挂起函数时,该函数会返回一个`Continuation`对象,该对象可以被用来在稍后的时间点恢复挂起函数的执行。 Kotlin程的调度器会在适当的时候调用Continuation对象的`resume`方法来恢复挂起函数的执行。当一个挂起函数被恢复执行时,它会从上一次挂起的地方继续执行,直到函数结束或者再次遇到挂起点。 Kotlin程的实现原理可以用以下伪代码来说明: ```kotlin fun main() { launch { println("Hello") delay(1000) println("World") } } suspend fun delay(time: Long) { // 挂起当前程,等待一段时间 // 通过Continuation对象来恢复程的执行 suspendCoroutine<Unit> { continuation -> Timer().schedule(time) { continuation.resume(Unit) } } } fun launch(block: suspend () -> Unit) { // 创建一个新的程,并将其加入到调度器中 val coroutine = Coroutine(block) coroutine.start() } class Coroutine(private val block: suspend () -> Unit) { fun start() { block.startCoroutine(this) } } class Continuation(private val coroutine: Coroutine) { fun resume(value: Any?) { coroutine.resume(value) } } suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T { // 挂起当前程,等待Continuation对象被调用 // 通过Continuation对象来恢复程的执行 return suspendCoroutineOrReturn { continuation -> block(Continuation(coroutine)) } } ``` 在上面的代码中,`launch`函数用于创建一个新的程,并将其加入到调度器中。`Coroutine`类表示一个程,`start`方法用于启动程的执行。`suspendCoroutine`函数用于挂起当前程,并等待Continuation对象被调用以恢复程的执行。`delay`函数使用`suspendCoroutine`函数来实现挂起功能,并在一定时间后恢复程的执行。 Kotlin程的实现原理比较复杂,但是开发者只需要关注如何使用程来实现异步操作即可。通过使用程,开发者可以编写出更加简洁、易于理解、易于维护的异步代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AD钙奶-lalala

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值