kotlin协程中的createCoroutineUnintercepted()
先调用createCoroutineUnintercepted()创建,然后在启动
// 带receiver
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
)
// 不带receiver
public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
completion: Continuation<T>
)
两个方法唯一的区别就是receiver,其他都是一样的,接下来只分析带receiver版本。
每次调用这个方法的时候,它返回一个Continuation类型,它一个全新的状态机,后续调用它的continuation.resumeWith(Unit)方法可以让(suspend R.() -> T)对应的可被挂起的 计算执行起来。当可被挂起计算执行完毕以后:
执行完毕表示:
1. 正常执行完毕
2. 抛出了未捕获异常
3. 执行过程中拿到后续的续体,但是恢复续体时调用resumeWith(Result.failure(exception))
参数completion的resumeWith()方法会被回调。receiver参数则会被不同方式传递给这个可被挂起的计算,好让它在其中可以被使用。
我们可以直接调用createCoroutineUnintercepted()方法返回的Continuation类型也可以间接调用它
源码分析
// IntrinsicsJvm.kt
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> {
val probeCompletion = probeCoroutineCreated(completion)
return if (this is BaseContinuationImpl)
create(receiver, probeCompletion)
else {
createCoroutineFromSuspendFunction(probeCompletion) {
(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
}
}
}
val probeCompletion = probeCoroutineCreated(completion)
probeCompletion有两种情况:
- probeCompletion === completion
- Continuation类型的包装类,当调用它的resumeWith()方法时,后续会调用到completion.resumeWith()
probeCoroutineCreated()主要起一个探测作用,不影响主要逻辑
(suspend R.() -> T)分为3种情况:
- 指向BaseContinuationImpl类型的状态机
- 指向函数引用
- 指向一个java代码实现的挂起方法
createCoroutineUnintercepted()方法内部会将1和2/3分开处理
指向BaseContinuationImpl类型的状态机
// block在运行时指向一个BaseContinuationImpl类型的状态机
val block: suspend String.() -> String = {
println("run block000")
"block000"
}
这种情况直接通过create(receiver, probeCompletion)方法创建一个状态机并返回
//可被挂起计算被调用的路径
// 直接或者间接调用
impl.resumeWith(Result.success(Unit))
|
V
impl.invokeSuspend(Result.success(Unit))
|
V
(suspend R.() -> T)对应的可被挂起的计算
查看反编译后的字节码,create(receiver, probeCompletion)中receiver参数可以在lambda中通过this关键字被引用到。
//reciver的赋值流程
//1 创建状态机时
createCoroutineUnintercepted(receiver,competion)
|
V
create(receiver,probeCompletion) // 返回stateMachine
|
V
stateMachine.L$0 = receiver // 保存receiver到编译器生成的字段中
//2. 执行可被挂起计算时
stateMachine.resumeWith(Result.success(Unit))
|
V
stateMachine.invokeSuspend(Result.success(Unit))
|
V
String var2 = (String)this.L$0; // 通过L$0引用receiver
System.out.println("receiver " + var2); // 对应编译前的代码:println("run block000")
接下来看看completion参数如何被使用
//1 在创建状态机时保存到BaseContinuationImpl.completion字段中
val probeCompletion = probeCoroutineCreated(completion)
// 1. probeCompletion === completion
// 2. probeCompletion包装completion
|
V
createCoroutineUnintercepted(receiver,completion)
|
V
create(receiver,probeCompletion)
|
V
状态机实际类型(value:Continuation)
|
V
// 因为看不到代码所以不确定,调用哪个
// 不过如果probeCompletion不为空则一定调用的是第一个
Suspendlambda(arity,probeCompletion)或者(arity)
|
V
ContinuationImpl(probeCompletion,_context)
|
V
BaseContinuationImpl(probeCompletion)
|
V
create(receiver,probeCompletion)执行完毕,返回状态机stateMachine
//2 当可被挂起计算执行完毕时,调用它的resumeWith()方法的调用路径
可被挂起计算执行完毕
|
V
stateMachine.completion.resumeWith()
// stateMachine.completion实际是probeCompletion
|
V
1.probeCompletion包装了completion,后续调用completion.resumeWith()
2.probeCompletion === completion,completion.resumeWith()被直接调用到
指向函数引用
@OptIn(InternalCoroutinesApi::class)
fun main() {
val block1: suspend String.() -> String = String::getImage
val block2: suspend String.() -> String = String::getText
// ...
}
// 挂起函数,带receiver
public suspend fun String.getImage(): String {
return suspendCoroutineUninterceptedOrReturn {
thread {
it.resumeWith(Result.success("getImage000"))
}
COROUTINE_SUSPENDED
}
}
// 挂起函数,有一个参数
public suspend fun getImage2(id:String): String {
return suspendCoroutineUninterceptedOrReturn {
thread {
it.resumeWith(Result.success("getImage000"))
}
COROUTINE_SUSPENDED
}
}
// 非挂起函数
public suspend fun String.getText():String{
return "getText000"
}
如果(suspend R.() -> T)不是BaseContinuationImpl类型,则会创建一个ContinuationImpl的包装类,在这个包装类的invokeSuspend()方法中将(suspend R.() -> T) 强转为Function2<R,Continuation,Any?>然后调用它的invoke()方法,开始执行(suspend R.() -> T)对应的可被挂起的计算。
// 可被挂起计算被调用的流程
// 直接或者间接调用
包装类.resumeWith(Result.success(Unit))
|
V
包装类.invokeSuspend(result)
|
V
Function2<R,Continuation<T>,Any?>.invoke(result)
|
V
(suspend R.() -> T)对应的可被挂起的计算
根据compeltion.context会选择2个包装类中的一个,这2个包装类的流程大致一样,ai说是性能优化。
createCoroutineUnintercepted(receiver,completion)中的receiver参数回先被捕获到闭包中,然后在调用Function2.invoke(receiver,completion) 时传递给可被挂起计算
// 1. receiver的捕获
createCoroutineUnintercepted(receiver,completion)
|
V
receiver被捕获到Function2.invoke()所在的闭包中
// 2. 将receiver传递给可挂起计算
包装类.invokeSuspend(result)
|
V
捕获receiver的闭包(result)
|
V
(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
|
V
??????
|
V
可被挂起计算中可以引用到这个receiver
// 在可被挂起计算中引用这个receiver
public suspend fun String.getImage(): String {
// 通过this引用
}
// 挂起函数,有一个参数
public suspend fun getImage2(id:String): String {
// 通过第一个参数引用
// 这里是"id"
}
public suspend fun String.getText():String{
// 通过this引用
}
其中"??????"部分不知道发生了什么。接下来看看completion参数:
// completion的赋值
//1 在创建状态机时保存到BaseContinuationImpl.completion字段中
val probeCompletion = probeCoroutineCreated(completion)
// 1. probeCompletion === completion
// 2. probeCompletion包装completion
|
V
createCoroutineFromSuspendFunction(probeCompletion,block)
|
V
// 或者ContinuationImpl(probeCompletion)
RestrictedContinuationImpl(probeCompletion)
|
V
// 赋值给了completion字段
BaseContinuationImpl(probeCompletion)
createCoroutineUnintercepted(receiver,completion)的completion被被直接或者包装成probeCompletion赋值给包装类的completion字段。
// 调用到completion.resumeWith()的路径
可被挂起计算执行完毕
|
V
包装类.compeletion.resumeWith()
//如果probeCoroutineCreated()直接返回的completion
//这里直接就调用createCoroutineUnintercepted()的compltion的resumeWith()方法
|
V
completion.resumeWith()
当可被挂起计算执行完毕,回调用包装类.compeletion.resumeWith(),这回让createCoroutineUnintercepted()的compltion的resumeWith()方法被直接 或者间接的调用到。
为什么可以强转为Function2<R,Continuation,Any?>呢?可以通过block::class.java反射来查看实际类型,目前可以推测到如下程度:
// 泛型参数T目前不知道怎么推测
Function2<R,Continuation,Any?>
为什么这种情况下需要将(suspend R.() -> T)包装到一个状态机呢?因为kotlin框架内部实现了续体的拦截机制:
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
(this as? ContinuationImpl)?.intercepted() ?: this
显然这种拦截机制的前提是续体必须是ContinuationImpl子类型,但是此时(suspend R.() -> T)指向的实际类型并不是ContinuationImpl子类型(通过反射得知)所以为了让这种情况下 它对应的可挂起计算中也没有办法能拿到支持挂起的ContinuationImpl子类型续体,于是就将它包装到了一个状态机中:
public suspend fun String.getImage3(): String {
return suspendCoroutineUninterceptedOrReturn {
//
val intercepted = it.intercepted()
"getImage3333"
}
}
你也可以直接将一个ContinuationImpl子类型的续体直接传递给Function2<String,Continuation,Any?>.invke(receiver,completion), 这样对应可被挂起计算中就能拿到这个子类型来进行拦截了。
指向一个java代码实现的挂起方法
例子参考java中实现的挂起方法引用
启动
import kotlin.coroutines.intrinsics.createCoroutineUnintercepted
import kotlin.coroutines.intrinsics.intercepted
fun main() {
val block: suspend String.() -> String = {
println("run block000")
"block000"
}
val receiver = "block000receiver"
val completion = object : Continuation<String> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
// 如果要使用intercepted()方法
// get() = Dispatchers.IO
override fun resumeWith(result: Result<String>) {
println("completion resule $result")
}
}
val coroutine = block.createCoroutineUnintercepted(receiver, completion)
// 直接调用
// coroutine.resumeWith(Result.success(Unit))
// 间接调用例子
coroutine.intercepted().resumeCancellableWith(Result.success(Unit))
while (true) {
}
}