前语

在文章 挂起函数原理解析中,咱们把挂起函数经过CPS转化后,经过多出的Continuation变量,以及巧妙的状态机模型,来完结挂起函数的调用。

在文章 # 协程(16) | 优雅地完结一个挂起函数中,咱们经过suspendCancellableCoroutine{}高阶函数里边露出的Continuation接口对象,调用其resume办法来完结挂起函数,往往用于完结函数内部的逻辑的

所以这儿的关键便是Continuation接口和suspendCancellableCoroutine{}办法,在之前文章咱们剖析过该接口的效果,咱们以它笼统挂起函数挂起和康复的视点来看的,这篇文章咱们先来看看Continuation的效果。

正文

话不多说,咱们先来看看这个Continuation到底有什么用。

Continuation的效果

其实咱们在前面就运用过Continuation的第一种用法了,就比如下面代码,咱们运用扩展函数的办法,让咱们的程序支持了挂起函数:

/**
 * 把本来的[CallBack]方式的代码,改成协程款式的,即消除回调,运用挂起函数来完结,以同步的办法来
 * 完结异步的代码调用。
 *
 * 这儿的[suspendCancellableCoroutine] 翻译过来便是挂起可撤销的协程,因为咱们需求成果,所以
 * 需求在合适的时机康复,而康复便是经过[Continuation]的[resumeWith]办法来完结。
 * */
suspend fun <T : Any> KtCall<T>.await(): T =
    suspendCancellableCoroutine { continuation ->
        //开始网络恳求
        val c = call(object : CallBack<T> {
            override fun onSuccess(data: T) {
                //这儿扩展函数也是奇葩,简略重名
                continuation.resume(data)
            }
            override fun onFail(throwable: Throwable) {
                continuation.resumeWithException(throwable)
            }
        })
        //当收到cancel信号时
        continuation.invokeOnCancellation {
            c.cancel()
        }
    }

这个是在咱们之前文章中介绍过的比如,能够把Callback消除,运用挂起函数来完结以同步的办法写异步的代码。在这儿的中心便是经过suspendCancellableCoroutine{}高阶函数所露出的continuation,向外部传递数据。

这个比如或许有点杂乱,咱们写个更简略的比如:

fun main() = runBlocking {
    val length = getLengthSuspend("Hello")
    println(length)
}
/**
 * 运用[suspendCancellableCoroutine]完结挂起函数,模仿耗时后回来文本的长度
 * @param text 测验文本
 * */
suspend fun getLengthSuspend(text: String): Int =
    suspendCancellableCoroutine { continuation ->
        thread {
            //模仿耗时
            Thread.sleep(2000)
            continuation.resume(text.length)
        }
    }

在这个比如中,你或许有点疑问,为什么挂起函数中以continuation.resume的办法异步传出的成果,在main()函数调用时,就能够收到成果呢?

这时咱们运用挂起函数原理那节的常识,对上面main()函数的调用进行改写:

fun main(){
    val func = ::getLengthSuspend as (String,Continuation<Int>) -> Any?
    func("Hello",object : Continuation<Int>{
        override val context: CoroutineContext
            get() = EmptyCoroutineContext
        override fun resumeWith(result: Result<Int>) {
            println(result.getOrNull())
        }
    })
    //避免程序退出
    Thread.sleep(5000)
}

其实这儿的Continuation就相似与Callback,所以回到了之前刚学习挂起函数的一个观念:挂起函数的实质还是Callback

综上所述,Continuation的效果就适当于Callback它既能够用于完结挂起函数,向挂起函数外传递成果;也能够用于调用挂起函数,咱们能够创立Continuation的匿名内部类,来接纳挂起函数回来的成果

suspendCoroutineUnintercepedtOrReturn函数解析

已然知道了Continuation的效果,就适当于是一个Callback,那挂起函数的中心就落到了咱们常用的suspendCoroutine{}suspendCancellableCoroutine{}这2个高阶函数身上了,经过简略检查源码,这2个函数的中心都是调用了suspendCoroutineUninterceptedOrReturn{}这个长长的办法。

咱们检查一下干函数的源码:

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")
}

会发现它是一个抛出反常的办法,并且没有任何完结,这是因为这个函数是由Kotlin编译器来完结的。

这儿咱们会发现,这个类所在的办法是Intrinsic.kt,并且上面函数抛出的反常也说该办法是intrinsic的,这个英语单词的意思是固有、实质的意思,但是在这儿表示的是编译器范畴的一个术语,能够理解为内建,即suspendCoroutineUninterceptedOrReturn{}函数是一个编译器内建函数,它是由Kotlin编译器来完结的

关于这个函数的详细完结,咱们就不持续往下深究了,但是咱们发现它的参数block能够接纳一个Lambda,一起这个block的函数类型是(Continuation<T>) -> Any?,这个Any?是否有一点了解,在挂起函数原理那一节中咱们经过CPS转化后得到的状态机代码,便是经过挂起函数的回来值来切换状态机的。

当时咱们说的这个回来值类型之所以是Any?,是因为当挂起函数是伪挂起函数时,回来函数值;当是挂起函数时,回来固定值,表示它已经被挂起。

其实上面block这个lambda的函数类型回来值,也是这个意思,咱们来验证一下,测验代码如下:

/**
 * 测验完结伪挂起函数,这儿值得注意的是[suspendCoroutineUninterceptedOrReturn]的函数类型
 * [block]是经过[crossinline]润饰的,经过该关键字润饰的高阶函数类型,在里边是不能直接运用[return]的,
 * 必须要return特定的效果域。
 * */
private suspend fun testNoSuspendCoroutine() =
    suspendCoroutineUninterceptedOrReturn<String> { continuation ->
        return@suspendCoroutineUninterceptedOrReturn "Hello"
    }
fun main() = runBlocking {
    val length = testNoSuspendCoroutine()
    println(length)
}

在这儿咱们没有经过continuation.resume()办法来设置回来值,而是直接return了成果Hello,关于上面注释中说的crossinline关键字,能够检查文章:# Kotlin的inline、noinline、crossinline全面剖析1。

上面代码履行成果能够直接得到Hello,咱们能够直接把上面代码进行反编译,如下:

private static final Object testNoSuspendCoroutine(Continuation $completion) {
   int var2 = false;
   if ("Hello" == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
      DebugProbesKt.probeCoroutineSuspended($completion);
   }
   return "Hello";
}

看到这儿你是不是茅塞顿开了,咱们前面的suspendCoroutineUninterceptOrReturn{}办法没了,转而是普通的函数,在这儿边加入了判别是否挂起的逻辑,然后直接回来成果

这个办法结合 # 协程(15) | 挂起函数原理解析中说的状态机模型,当挂起函数调用这个办法时,发现对错挂起,就能够直接回来成果了。

这个时候,咱们来写一个真实的挂起函数:

/**
 * 这儿真实运用[Continuation]来往挂起函数外传递了值
 * 一起,函数回来值范围了挂起标志位
 * */
private suspend fun testSuspendCoroutine() =
    suspendCoroutineUninterceptedOrReturn<String> { continuation ->
        thread {
            Thread.sleep(1000)
            continuation.resume("Hello")
        }
        return@suspendCoroutineUninterceptedOrReturn kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
    }
fun main() = runBlocking {
    val length = testSuspendCoroutine()
    println(length)
}

这儿,咱们运用continuation向外部传递了函数回来值,一起在return时回来了代表挂起函数被挂起的标志位。

咱们依旧把上面代码进行反编译,得到如下代码:

private static final Object testSuspendCoroutine(Continuation $completion) {
   int var2 = false;
   //注释1
   ThreadsKt.thread$default(false, false, (ClassLoader)null, (String)null, 0, (Function0)(new KtContinuationKt$testSuspendCoroutine$2$1($completion)), 31, (Object)null);
   //注释2
   Object var10000 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
   if (var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
      DebugProbesKt.probeCoroutineSuspended($completion);
   }
    //注释3
   return var10000;
}
final class KtContinuationKt$testSuspendCoroutine$2$1 extends Lambda implements Function0 {
   // $FF: synthetic field
   final Continuation $continuation;
   // $FF: synthetic method
   // $FF: bridge method
   public Object invoke() {
      this.invoke();
      return Unit.INSTANCE;
   }
   public final void invoke() {
       //注释4
      Thread.sleep(1000L);
      Continuation var1 = this.$continuation;
      String var2 = "Hello";
      Companion var10001 = Result.Companion;
      //注释5
      var1.resumeWith(Result.constructor-impl(var2));
   }
   KtContinuationKt$testSuspendCoroutine$2$1(Continuation var1) {
      super(0);
      this.$continuation = var1;
   }
}

上面代码不难理解,剖析如下:

  • 注释2和注释3,会把var10000赋值为挂起标志位,然后直接return该值,调用它的挂起函数就会知道该函数已经被挂起,即会等候进入新一轮状态机。
  • 注释1、4、5便是开启了一个线程,在线程经过1000ms后,经过continuationresumeWith办法把值传递到函数外。

这儿再结合状态机的模型,外部挂起函数调用该函数时,就会发现该函数是真挂起了,会传递唯一的continuation进去,等候resumeWith回调,从而进入下一个状态机分支

到这儿,咱们也能够总结一下suspendCoroutineUninterceptedOrReturn{}这个高阶函数的效果了:它能够将挂起函数中的Continuation以参数的方式露出出来,在它的lambda中,咱们能够直接回来成果,这时是一个伪挂起函数;也能够运用回来COROUTINE_SUSPENDED这个挂起标志位,然后运用resume()传递成果

最重要的是,里边的状态机逻辑是Kotlin编译器帮咱们完结的,经过反编译后咱们能够发现该函数不见了,取而代之的是上一节文章所说的状态机原理。

suspendCoroutine{}suspendCancellableCoroutine{}高阶函数

经过本篇文章前面的学习,咱们基本就非常明晰地明白挂起函数的原理了,但是咱们经常运用的用来完结挂起函数的高阶函数却是suspendCoroutine{}suspendCancellableCoroutine{},咱们来看看这2个函数的源码:

public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T {
    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
    return suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
        val safe = SafeContinuation(c.intercepted())
        block(safe)
        safe.getOrThrow()
    }
}
public suspend inline fun <T> suspendCancellableCoroutine(
    crossinline block: (CancellableContinuation<T>) -> Unit
): T =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
        cancellable.initCancellability()
        block(cancellable)
        cancellable.getResult()
    }

剖析如下:

  • 首先这2个办法都是运用suspendCoroutineUninterceptedOrReturn{}来完结的,仅仅对代码履行加了一些额外判别,比如suspendCoroutine{}中调用block(safe)加了安全判别,在suspendCancellableCoroutine{}中调用block(cancellable)来看一响应撤销。
  • 其次便是这俩个办法的回来值,都是T,这就能够极大地减少咱们运用的成本。究竟如果直接运用suspendCoroutineUninterceptedOrReturn{}的话,需求开发者知道挂起函数的状态机原理,指定回来特定的回来值。

总结

学习完本篇文章,再结合之前挂起函数的原理,我相信肯定会有一种茅塞顿开的感觉。简略概况便是Continuation实质便是Callback,既能够用于完结挂起函数,对外传递回来值;也能够完结其接口,接纳挂起函数的回来值。

suspendCoroutineUninterceptedOrReturn是编译器内建函数,是咱们能接触的创立挂起函数最底层的函数了,该函数首要便是完结了之前文章所介绍的状态机逻辑。