Kotlin协程分析(二)——suspendCoroutineUninterceptedOrReturn

一、简介

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

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

二、分析

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

图中标记 1continuation 就是我们创建出来的 协程对象,还记得他是个啥玩意儿吗?这里回顾一下,他是一个 SafeContinuation,并且它持有一个名字为 delegateContinuation 属性,这个 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 中的continuationit 是同一个 协程对象 吗?答案当然是否定的,没认真阅读文章的同学再往回看吧……

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

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

关于这个问题……涉及到了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 因为其不凡的特性,使得它的地位极高,是一个很有存在感的函数。对于整个协程来说意义重大,花时间去了解它是非常值得的一件事情。

那么关于协程我们需要学习的东西还有很多,希望我们能够一起学习一同进步!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/catzifeng/article/details/108913942