Kotlin协程源码分析-2 调用挂起函数

男娘i 2023-07-17 14:18 122阅读 0赞

引语

我们假设有如下挂起函数

  1. //com.example.studycoroutine.chapter.CoroutineRun.kt
  2. suspend fun suspendFun(): Int {
  3. return 1;
  4. }

上面的代码在kotlin编译的时候会变成如下函数

  1. @Nullable
  2. public static final Object suspendFun( Continuation completion) {
  3. return Boxing.boxInt(1);
  4. }

也就是说所有的挂起函数(suspend关键字修饰的函数)在编译后会变成一个传入Continuation函数.

多看一个例子:

  1. suspend fun suspendFunHaveOneParameter(msg: String): String {
  2. return "incoming parameter [$msg]";
  3. }

编译后

  1. @Nullable
  2. public static final Object suspendFunHaveOneParameter( String msg, Continuation completion) {
  3. return "incoming parameter [" + msg + ']';
  4. }

Continuation是什么

首先看看看类定义(先看一眼即可)

  1. //kotlin.coroutines.Continuation.kt
  2. /**
  3. * Interface representing a continuation after a suspension point that returns a value of type `T`.
  4. */
  5. @SinceKotlin("1.3")
  6. public interface Continuation<in T> {
  7. /**
  8. * 协程上下文。
  9. * 上下文就是用来装数据的集合,比如Android 的上下文获得图片资源等
  10. */
  11. public val context: CoroutineContext
  12. /**
  13. * 挂起恢复的回调,先了解即可
  14. */
  15. public fun resumeWith(result: Result<T>)
  16. }
  • 这里涉及两个问题:
  1. Continuation是什么
  2. 在我们自己编写的kotlin代码中怎么拿到这个Continuation参数

当然这里我们先缓缓这两个问题,我们怎么调用这两个函数?
在常规的情况的我们无法直接调用对应的suspend函数(如下图),因为我们知道编译后函数会多一个Continuation参数,所以现在大家对于为何不能直接调用suspend函数调用理解的更加深刻。所以我们想调用此类函数,就必须传入Continuation参数(或在其他suspend函数调用会自动依赖编译器生成传入,后续文章再讲)。
无法常规调用suspend函数

  • 总结能调用方法有两种:
  1. suspend函数中调用其他suspend函数(编译器黑魔法,后续文章讲解本文跳过)
  2. 自己创建Continuation ,进行调用( 本文讲解主要点)

创建Continuation调用suspend函数

  • 如何创建Continuation?

我们可以从Kotlin 核心库源码分析得到我们想要的结果。
添加依赖:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4'(这个只是封装了协程工具类,不需要也可以写出协程。比如 GlobalScope.launch就封装在这个库)

然后分析如下代码调用:

  1. GlobalScope.launch {
  2. println("MainActivity.onCreate")
  3. }

查看launch函数声明

  1. //kotlinx.coroutines.Builders.common.kt
  2. public fun CoroutineScope.launch(
  3. context: CoroutineContext = EmptyCoroutineContext,
  4. start: CoroutineStart = CoroutineStart.DEFAULT,
  5. block: suspend CoroutineScope.() -> Unit
  6. ): Job {
  7. val newContext = newCoroutineContext(context)
  8. val coroutine = if (start.isLazy)
  9. LazyStandaloneCoroutine(newContext, block) else
  10. StandaloneCoroutine(newContext, active = true)
  11. coroutine.start(start, coroutine, block)
  12. return coroutine
  13. }

来看下核心的几行代码,下面的代码返回一个类对象实现类Continuation接口的类而已(这个后面我们会自己实现)

  1. val coroutine = if (start.isLazy)
  2. LazyStandaloneCoroutine(newContext, block) else
  3. StandaloneCoroutine(newContext, active = true)

由于文本不讲解基础使用,所以继续跟源码会调用CoroutineStart类的invoke函数

  1. public operator fun <T> invoke(block: suspend () -> T, completion: Continuation<T>) =
  2. when (this) {
  3. CoroutineStart.DEFAULT -> block.startCoroutineCancellable(completion)
  4. CoroutineStart.ATOMIC -> block.startCoroutine(completion)
  5. CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(completion)
  6. CoroutineStart.LAZY -> Unit // will start lazily
  7. }

我们这里只看如下部分
CoroutineStart.ATOMIC -> block.startCoroutine(completion)
继续跟进去startCoroutine函数会调用到kotlin.coroutines.Continuation.kt

  1. public fun <T> (suspend () -> T).startCoroutine(
  2. completion: Continuation<T>
  3. ) {
  4. createCoroutineUnintercepted(completion).intercepted().resume(Unit)
  5. }

看得到这里即可。

  • createCoroutineUnintercepted 创建一个continuation 传入一个completion对象,这个对象是调用suspend函数会回调到此对象的resumeWith
  • intercepted 拦截器可以用于切换线程等,这里可以忽视
  • resume 调用suspend函数并传入对应的continuation
  • 以上便是核心库的调用逻辑,总结上面的源码分析结果:
  1. 创建一个Continuation对象
  2. 然后调用Continuation.kt对于suspendlambda的扩展函数startCoroutine函数即可(这里是对lambda对象扩展,不是函数你之后可以在这个lambda表达式中调用suspend函数)
  3. suspendlambda中调用其他suspend函数

注意:suspendlambda不等价suspend函数!!!!!

我们来自己实践下
第一步创建个Continuation对象

  1. //com.example.studycoroutine.chapter.two.MyCoroutine.kt
  2. class MyCoroutine() : Continuation<String> {
  3. override fun resumeWith(result: Result<String>) {
  4. logD("MyCoroutine 回调resumeWith 返回的结果 " + result.getOrNull())
  5. }
  6. override val context: CoroutineContext
  7. get() = kotlin.coroutines.EmptyCoroutineContext
  8. }

第二步和第三部调用Continuation.ktstartCoroutine函数即可

  1. //com.example.studycoroutine.chapter.two.CoroutineRun.kt
  2. suspend fun mySuspendFun(): String {
  3. return "hello";
  4. }
  5. fun testOne(){
  6. val myCoroutineFun: suspend () -> String = {
  7. logD("返回 hello结果")
  8. mySuspendFun()
  9. }
  10. val myCoroutine = MyCoroutine()
  11. val coroutine = myCoroutineFun.startCoroutine(myCoroutine)
  12. }

输出:

  1. [main] 返回 hello结果
  2. [main] MyCoroutine 回调resumeWith 返回的结果 hello

当然还有一些其他使用方式Continuation.kt

  1. //kotlin.coroutines.Continuation.kt
  2. //创建一个createCoroutine后面调用resume即可调用挂起函数
  3. public fun <T> (suspend () -> T).createCoroutine(
  4. completion: Continuation<T>
  5. ): Continuation<Unit> =
  6. SafeContinuation(createCoroutineUnintercepted(completion).intercepted(), COROUTINE_SUSPENDED)
  7. //创建一个createCoroutine并直接调用resume
  8. public fun <R, T> (suspend R.() -> T).startCoroutine(
  9. receiver: R,
  10. completion: Continuation<T>
  11. ) {
  12. createCoroutineUnintercepted(receiver, completion).intercepted().resume(Unit)
  13. }

于是掉用suspend 函数还可以这样写

  1. //com.example.studycoroutine.chapter.two.CoroutineRun.kt
  2. fun testOne(){
  3. val myCoroutineFun: suspend () -> String = {
  4. logD("返回 hello结果")
  5. mySuspendFun()
  6. }
  7. val myCoroutine = MyCoroutine()
  8. //这种写法是下面的封装而已
  9. //myCoroutineFun.startCoroutine(myCoroutine)
  10. val createCoroutine = myCoroutineFun.createCoroutine(myCoroutine)
  11. createCoroutine.resume(Unit)
  12. }

发表评论

表情:
评论列表 (有 0 条评论,122人围观)

还没有评论,来说两句吧...

相关阅读

    相关 3.-函数

    上一篇,我们知道了非阻塞挂起的核心是要执行一个挂起函数,挂起函数的作用就是启动线程执行耗时任务,耗时任务执行完毕,通知调用线程继续执行后续的代码。那么我们如何定义挂起函数呢?有

    相关 2.-理解非阻塞

    上一篇,我们了解到了协程的作用,以及协程到底在干啥,有了对上一篇的了解,我们继续学协程就稍微的轻松了(只能说稍微轻松)。 如何使用协程 如何使用协程呢? 1.协