在kotlin协程中将非挂起函数类型赋值给挂起函数类型
问题
private fun fetchData(): String {
println("fetchData")
return "this is data!"
}
private fun invokeFetchImageCase() {
// completion
val completion = object : Continuation<String> {
override val context: CoroutineContext = EmptyCoroutineContext
override fun resumeWith(result: Result<String>) {
println("completion2 result ${result.getOrThrow()}")
}
}
// 将::fetchImage这个非挂起函数类型赋值给挂起函数类型
// 本来是Function0,编译器偷偷做了工作,将他变成了Function1
val block: suspend () -> String = ::fetchImage
// 并且可以强转为Function1<Continuation<String>, Any?>(粗略)
// 调用invoke(it)以后fetchImage函数本身的方法体会被执行,然后返回结果
// 就好像在调用一个带挂起关键字但是永远不会挂起的方法
val result = (block as Function1<Continuation<String>, Any?>).invoke(completion)
println("asInvokeCase result $result")
}
private fun fetchData(): String {
println("fetchData")
return "this is data!"
}
val block: suspend () -> String = ::fetchImage
注意“::fetchImage”的类型为"() -> String",但是kotlin语法却允许将这个非挂起函数类型赋值给"suspend () -> String"
通过block::class.java查看block运行时类型
// block::class.java.superclass
block运行时类型 -> AdaptedFunctionReference -> Object
// block::class.java.interfaces
Function1
SuspendFunction
// block::class.java.interface
invoke(java.lang.Object)
invoke(kotlin.coroutines.Continuation)
..(其他若干方法)
可以看到block运行时类型实现了Function1,然后多了2个invoke()方法,其中invoke(java.lang.Object)显然是由于泛型导致的桥接方法。
那么在运行时,fetchData中的代码如何被执行起来呢?
执行block
如果想让fetchData()函数的函数体被执行,只需调用block执行的运行时类型的invoke()方法即可:
- 方式1:在挂起方法中调用block()
val block: suspend () -> String = ::fetchData
runBlocking {
// 反编译以后可以看到实际会调用
// block.invoke(当前状态机)
block()
}
// 输出:
// fetchData
// asInvokeCase result this is data!
- 方式2:通过as关键字强转为Function1,然后调用invoke(continuation)
val block: suspend () -> String = ::fetchData
val completion = completion<String> {
println("completion2 result $it")
}
// 强转为Function1
val function1 = block as Function1<Continuation<String>, Any?>
// 调用invoke
val result = function1.invoke(completion)
println("asInvokeCase result $result")
// 输出:
// fetchData
// asInvokeCase result this is data!
观察输出结果发现:
- fetchData的函数体被调用了
- 传递给invoke方法的completion不会被调用
另外,强转类型“Function1<Continuation, Any?>”是参考kotlin协程源码得来的:
// IntrinsicsJvm.kt
public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
completion: Continuation<T>
): Continuation<Unit> {
val probeCompletion = probeCoroutineCreated(completion)
return if (this is BaseContinuationImpl)
create(probeCompletion)
else
createCoroutineFromSuspendFunction(probeCompletion) {
// 参考的这里
(this as Function1<Continuation<T>, Any?>).invoke(it)
}
}
block运行时类型伪代码
结合block::class.java与上面的测试代码,block运行时类型伪代码如下:
Function1<Continuation<String>, Object> block = new Function1<Continuation<String>, Object>() {
@Override
public Object invoke(Continuation<String> continuation) {
return fetchData();
}
};
编译器相当于至少干了如下事情:
- 让这个类型实现了Function1<Continuation, Object>
- invoke()方法内部直接调用fetchData()然后直接返回,并不会回调continuation
为什么kotlin中运行这种赋值?
让非挂起函数可以直接被当成挂起函数使用,减少模版代码
其他例子
val b = {
println("run!")
"100"
}
// b::class.java.interfaces
// Function0
kele(b)
private fun kele(block: suspend () -> String) {
// block::class.java.interfaces
// Function1
// SuspendFunction
//神奇,block和外面的b不一样了
val completion = object : Continuation<String> {
override val context: CoroutineContext = EmptyCoroutineContext
override fun resumeWith(result: Result<String>) {
println("completion 回调:${result.getOrThrow()}") // 挂起恢复后触发
}
}
block.startCoroutineUninterceptedOrReturn(completion)
}
注意:block和外面的b不一样了!