Kotlin协程分析(二)——suspendCoroutineUninterceptedOrReturn

    科技2022-07-11  98

    文章目录

    一、简介二、分析三、suspendCoroutineUninterceptedOrReturn 怎么做到的?四、战略作用(重点)!!!1、两个`resume()` 为啥没有走两次回调2、为啥里边的**continuation**的`resume()` 会在返回值里呢?3、返回值为什么是 COROUTINE_SUSPENDED 五、简单的用例(必读)六、小结

    一、简介

    这个函数的作用 至关重要。我们先回想之前创建 协程(Continuation) 的过程,是通过一个 suspend<R>()->R 的函数调用 createCoroutine() 生成的,而我们可以利用 suspendCoroutineUninterceptedOrReturn() 函数在 suspend<R>()->R 方法中拿到 协程 的实例。

    听起来有点别扭,而且一脸懵逼……

    二、分析

    没关系,我举个例子(代码没有提示,所以截图):

    图中标记 1 的 continuation 就是我们创建出来的 协程对象,还记得他是个啥玩意儿吗?这里回顾一下,他是一个 SafeContinuation,并且它持有一个名字为 delegate 的 Continuation 属性,这个 delegate 才是真正干活的。 而图中标记 2 的对象,就是这个 delegate。

    三、suspendCoroutineUninterceptedOrReturn 怎么做到的?

    这里我分享一下我的发现,因为全网都没有原子性的答案,我也是一知半解……

    我们先看看函数:

    @SinceKotlin("1.3") @InlineOnly @Suppress("UNUSED_PARAMETER", "RedundantSuspendModifier") public suspend inline fun <T> suspendCoroutineUninterceptedOrReturn(crossinline block: (Continuation<T>) -> Any?): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } throw NotImplementedError("Implementation of suspendCoroutineUninterceptedOrReturn is intrinsic") }

    咱们不急,先看看函数的参数:block:(Continuation<T>) -> Any?,所以我们只需要搞懂这个block 在哪里调用了 block.invoke(continuation) 一切都简单明了了。 好!我们赶紧看第一行代码:contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } 抓耳挠腮啊……这是个啥玩意儿? 这其实是 Kotlin 1.3 的特性:约束,他的目的就是讲究一个人性(kotlin自带高阶函数apply、with等都用到过)……有兴趣的小伙伴可以自行搜索。

    这里的意思就是:约束 block 这个函数,只能调用一次。

    但是 约束 归约束,你这个 block 还是不会调用啊,所以我们继续往下看……

    然而第二行代码就是一个异常……什么鬼?并且直说了该方法是在 Kotlin 内部实现的,所以我们只能翻 Kotlin 源码,大海捞针一般的找……事实上我们还有一个线索,请大家看到该方法的注解:@InlineOnly。既然是内部实现,那么内部的实现肯定是依据注解来操作的,然后我就翻到这样的代码: 看看这熟悉的方法名称,再看看熟悉的注释,是不是一股亲切感?其实走到这里就差不多了,因为……

    接下来的路我走不动了……

    当我想继续往下深究这个 continuation 是怎么来的? block.invoke() 是如何调用的?我突然觉得自己能力有限……这一切都是和一个名叫 ASM 的框架相关,这玩意儿是 字节码级别 的编程,也就是说他能够直接操作 class 文件,细想一下如果你直接操作 class 文件,这不就是面向切面编程吗?这不就是AOP吗?既然是 AOP 我拿到一个对象应该很容易吧?我随便在一个方法内插入一些代码应该也很容易吧?

    所以关于 suspendCoroutineUninterceptedOrReturn 是如何实现的,我们的分析到此就结束吧……,有兴趣的小伙伴可以自行学习,来试探自己的学习上限吧……

    不过还是贴一张字节码的图吧,就随便看看:

    四、战略作用(重点)!!!

    介绍了这么多 suspendCoroutineUninterceptedOrReturn 的实现相关,其实我们还是要看重他的作用!

    其作用就是:我们能够通过调用 resume(value) 将值传递给返回值!

    相信大伙已经运行了一遍第二小节中的Demo1(还没运行?赶紧写去吧你!)

    对于结果肯定又是一脸懵逼!

    1、两个resume() 为啥没有走两次回调

    首先,我们先写个小Demo2: 如果我们连续调用两次 continuation.resume() 那么就会报错: 因为同一个 协程对象 第一次调用了 resume() 之后,状态机会到达 RESUMED 的状态,第二次就会走向异常了。

    那么我们第二小节的 Demo1 为什么没有出现异常呢?我们回到这个异常的触发点:同一个协程对象,那么Demo1 中的continuation 和 it 是同一个 协程对象 吗?答案当然是否定的,没认真阅读文章的同学再往回看吧……

    所以既然不是同一个对象,所以就不可能会有两次回调的发生吧?那也不是。因为 continuation 真正干活的是 delegate 和 it 是同一个对象。答案在源码处: 因为 suspendCoroutineUninterceptedOrReturn 方法会返回 COROUTINE_SUSPENDED,所以判断的时候会直接返回,走不到回调的代码位置。

    2、为啥里边的continuation的resume() 会在返回值里呢?

    关于这个问题……涉及到了ASM,所以答案只能在字节码文件中找……

    3、返回值为什么是 COROUTINE_SUSPENDED

    如果是非 COROUTINE_SUSPENDED 的话就会抛异常,观察源码我们发现:

    其实在第一次调用 continuation.resume() 的时候,不管成功还是失败都会调用 releaseIntercepted() 来标记状态,那就是协程已经结束了。如果还有第二次执行的话,走到 releaseIntercepted() 这个方法就会抛出异常,所以,如果不想第二次调用 resume() 会抛出异常,那就将其返回值设置成 COROUTINE_SUSPENDED ,这样的话就会在 2 处判断上是否相等,然后 return 掉。

    五、简单的用例(必读)

    前面你或许可以不看,但是这个例子你必须得看。

    我们来模仿一个 delay() 函数:

    private suspend fun delay2(time: Long) = suspendCoroutineUninterceptedOrReturn<Unit> { thread { Thread.sleep(time) it.resume(Unit) } return@suspendCoroutineUninterceptedOrReturn COROUTINE_SUSPENDED }

    用法:

    val suspendFun = suspend { println("step 1 :${System.currentTimeMillis()}") delay2(2000) println("step 2 :${System.currentTimeMillis()}") "hello" } ... ... //创建协程和执行的代码就省略了

    控制台输出: step 1 :1601740876419 step 2 :1601740878424 coroutine:hello

    通过这个简单的例子,大家肯定能够想到更多!!!原来线程之间的交互可以这样?那么你是不是突然眼前一亮?感觉你可以实现很多骚操作了?

    六、小结

    suspendCoroutineUninterceptedOrReturn 因为其不凡的特性,使得它的地位极高,是一个很有存在感的函数。对于整个协程来说意义重大,花时间去了解它是非常值得的一件事情。

    那么关于协程我们需要学习的东西还有很多,希望我们能够一起学习一同进步!

    Processed: 0.009, SQL: 8