前语
在文章 挂起函数原理解析中,咱们把挂起函数经过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后,经过
continuation
的resumeWith
办法把值传递到函数外。
这儿再结合状态机的模型,外部挂起函数调用该函数时,就会发现该函数是真挂起了,会传递唯一的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
是编译器内建函数,是咱们能接触的创立挂起函数最底层的函数了,该函数首要便是完结了之前文章所介绍的状态机逻辑。