Kotlin-Coroutines-Suspend原理
suspend 语法糖到底做了什么
我们常说的在runBlocking或者launch中会包裹的一层lambda代码,在JVM的概念中最终也会变为一个对象。那它到底变成了什么呢?
suspend fun test() {
delay(2000)
println("123")
}
fun main() {
runBlocking {
test()
}
}
public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
可以看到,runBlocking本质接受一个 suspend CoroutineScope.(),但suspend本来就是一个语法糖,那它实际是一个什么Java类型呢?在Hilt和反射中我们会去看class字节码然后反编译看生成的代理类,所以我们也来看看
使用Kotlinc来编译Main3.kt这个文件
kotlinc Main3.kt -cp kotlinx-coroutines-core-jvm-1.7.3.jar -d ../out/
记得使用-cp kotlinx-coroutines-core-jvm-1.7.3.jar
因为用到了coroutines相关依赖。
有个细节需要注意,在IDEA中,不会显示生成的匿名内部类文件,只有一个Main3Kt.class所以需要自己在文件目录下ls查看。
会发现有三个class文件
- Main3Kt.class: 主字节码文件
- Main3Kt$main$1.class: main
- Main3Kt$myLambda$1.class: 声明的suspend scope变量的字节码
先看下Main3Kt.class,发现其流程是
main(String[]) -> main() -> new $main$1.class & init (类似于 new $main1$(null))
那再来看Main3Kt$main$1.class,
首先会发现$main$1.class继承于SuspendLambda
你会发现其实main也是一个继承于SuspendLambda的类,似乎是因为main方法中调用了runBlocking的方法而升级成为了一个SuspendLambda,而只是在main方法中调用非协程方法不会导致额外的$main$类生成
suspend fun相关实际在最后会被包装成一个SuspendLambda类, 这个类其实是ContinuationImpl->BaseContinuationImpl->Continuation的实现类
final class org.example.Main3Kt$main$1 extends kotlin.coroutines.jvm.internal.SuspendLambda implements kotlin.jvm.functions.Function2<kotlinx.coroutines.CoroutineScope, kotlin.coroutines.Continuation<? super kotlin.Unit>, java.lang.Object>
其次来观察其构造方法
org.example.Main3Kt$main$1(kotlin.coroutines.Continuation<? super org.example.Main3Kt$main$1>);
相关信息
suspend fun在编译后都默认带有Continuation的构造参数
$main$1 class默认有一个Continuation的构造方法,但Main3Kt.class中初始化这个类时仅传递了空的Continuation,并在init时调用父类的init方法,也非常常规
$main1$.class中重写了invokeSuspend
create
invoke
的方法
关于invokeSuspend和create,在后边都会有讲到。这里可以理解为,invokeSuspend中包含了具体lambda的代码块内容,而create是为了后续真正地通过Continuation创建一个$main1$.class实例,而不再传入null。这里需要清楚的是,当invokeSuspend实际调用时,其实例类已经为通过create创建的新的实例了。这个实例中的continuation不为空,具体为某个AbstractCorotine的实现类
invokeSuspend中,有这么一句字节码
36: aload_0
37: checkcast #50 // class kotlin/coroutines/Continuation
// ....
45: invokestatic #56 // Method org/example/Main3Kt.test (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
也就是调用了Main3Kt.test中的方法并传入了当前的continuation,因此我们来看看test方法
与$main$1.class类似,在Main3Kt.class中的test方法,类似地
先判断传入的continuation是否是Main3Kt$test$1,(是)如果是,则赋值给局部变量,同时基于状态机switch逻辑
调用delay方法并最终再print(“123”)
因此这就是suspend fun在编译层的工作,还是做了非常非常多的工作,特别是生成代理类以及invokeSuspend的实现。同时还要理解suspend fun的完整调用流程,理解清楚create何时调用,以及SuspendLambda如何真正地执行,才能完整地理解这一小段代码的真实含义!
编译器优化
需要注意一点的是如果在suspend fun中没有使用到协程相关的函数,另一个挂起函数的调用,则编译器会将其优化为一个普通方法,即不会额外生成$xxx$匿名类