这个函数的作用 至关重要。我们先回想之前创建 协程(Continuation) 的过程,是通过一个 suspend<R>()->R 的函数调用 createCoroutine() 生成的,而我们可以利用 suspendCoroutineUninterceptedOrReturn() 函数在 suspend<R>()->R 方法中拿到 协程 的实例。
听起来有点别扭,而且一脸懵逼……
没关系,我举个例子(代码没有提示,所以截图):
图中标记 1 的 continuation 就是我们创建出来的 协程对象,还记得他是个啥玩意儿吗?这里回顾一下,他是一个 SafeContinuation,并且它持有一个名字为 delegate 的 Continuation 属性,这个 delegate 才是真正干活的。 而图中标记 2 的对象,就是这个 delegate。
这里我分享一下我的发现,因为全网都没有原子性的答案,我也是一知半解……
我们先看看函数:
@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(还没运行?赶紧写去吧你!)
对于结果肯定又是一脸懵逼!
首先,我们先写个小Demo2: 如果我们连续调用两次 continuation.resume() 那么就会报错: 因为同一个 协程对象 第一次调用了 resume() 之后,状态机会到达 RESUMED 的状态,第二次就会走向异常了。
那么我们第二小节的 Demo1 为什么没有出现异常呢?我们回到这个异常的触发点:同一个协程对象,那么Demo1 中的continuation 和 it 是同一个 协程对象 吗?答案当然是否定的,没认真阅读文章的同学再往回看吧……
所以既然不是同一个对象,所以就不可能会有两次回调的发生吧?那也不是。因为 continuation 真正干活的是 delegate 和 it 是同一个对象。答案在源码处: 因为 suspendCoroutineUninterceptedOrReturn 方法会返回 COROUTINE_SUSPENDED,所以判断的时候会直接返回,走不到回调的代码位置。
关于这个问题……涉及到了ASM,所以答案只能在字节码文件中找……
如果是非 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 因为其不凡的特性,使得它的地位极高,是一个很有存在感的函数。对于整个协程来说意义重大,花时间去了解它是非常值得的一件事情。
那么关于协程我们需要学习的东西还有很多,希望我们能够一起学习一同进步!