Kotlin协程源码分析-协程的启动
Posted 冬天的毛毛雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin协程源码分析-协程的启动相关的知识,希望对你有一定的参考价值。
1.简单启动协程Demo
本文例子代码地址:gitee.com/mcaotuman/k…
写个小demo,先记住怎么用,后面再分析源码。
三个挂起函数:
输出结果:
2.构建suspend修饰的lambda函数
2.1 suspend lambda
下面这段代码是suspend修饰lambda表达式:
val mySuspendLambda: suspend () -> String = {
//返回一个字符串hello world
val one = commonSuspendFun()
//返回一个字符串hello world2
val two = commonSuspendFun2()
//返回一个字符串hello world3
val three = commonSuspendFun3()
one+two+three
}
suspend lambda反编译成Java代码究竟长什么样子呢?
我们用JEB工具反编译看看:
我们根据代码定位到包three下
按右键解析字节码文件,得到反编译之后的Java代码:
由上图我们知道suspend lambda经过编译器的黑魔法,会编译成SuspendLambda
类(very important),继承关系如下图:
现在你看到BaseContinuationImpl
类和Continuation
接口,可能会一脸懵逼,究竟是干嘛的?没事,我们下面继续分析。
2.2 suspend函数
下面这段代码是suspend函数:
suspend fun commonSuspendFun(): String {
return "[hello world] "
}
反编译成Java代码长成这个样子:
没了suspend关键字,多了一个Continuation类型的参数。
这个Continuation又出现了,上面小节提到SuspendLambda会实现Continuation接口
Continuation到底是个什么东西?
来,看看源码:
这个Continuation接口不就是类似我们常写的CallBack吗?Continuation的resumeWith方法不就是相当于CallBack的onSuccess吗?没错的。
这个从挂起函数
转换成CallBack 函数
的过程,被称为:CPS 转换(Continuation-Passing-Style Transformation)。
不知道你有没有发现,返回值由原来的String 变成 Object!
由于 suspend 修饰的函数,既可能返回 CoroutineSingletons.COROUTINE_SUSPENDED
,也可能返回实际结果[hello world]
,甚至可能返回 null
,为了适配所有的可能性,CPS 转换后的函数返回值类型就只能是 Object
了。
3.创建协程:createCoroutine
我在如何查看Kotlin expect关键字在对应平台的实现(actual)? 这篇文章也曾提过createCoroutine的源码,他最后会调到createCoroutineUnintercepted
方法里。
根据上面的分析,我们知道suspend lambda在编译器的黑魔法编译之后,会变成SuspendLambda
类,实际上也是继承BaseContinuationImpl,那么他会直接走create方法。
实际上,是走到SuspendLambda类的构造函数
最后,走到BaseContinuationImpl,把SuspendLambda构建出来。
4.启动协程:resume
由步骤3得知,我们已经拿到了SuspendLambda实例,现在我们调用resume
扩展函数启动协程。
实际上是调用Continuation
的resumeWith
方法
那么我们debug一下BaseContinuationImpl的resumeWith
方法,就知道他是怎么启动协程的了。
**CoroutineRunKt $ testFunGetContinuation $ mySuspendLambda $1
**对应suspend lambda对象的
mySuspendLambda.这个对象主要作用就是用来维护 状态机,这个也是kotlin 协程的关键。
我们跟踪一下**CoroutineRunKt$testFunGetContinuation$mySuspendLambda$1
**的Java代码:
jeb demo版不允许复制代码,蛋疼,只能截图。
于是我手动打上了代码,并注释关键步骤:
public final Object invokeSuspend(Object arg8) {
String v3;
CoroutineRunKt.testFunGetContinuation.mySuspendLambda.1 this;
// v0:挂起函数返回标识SUSPEND_FLAG
Object v0 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
// (一)label一开始默认是0
switch (this.label) {
case 0:
ResultKt.throwOnFailure(arg8);
// (二)将label设置为1
this.label = 1;
// (三)调用第一个挂起函数,传入自己作为参数
// 如果返回SUSPEND_FLAG就返回函数。
// 当挂起函数用传入的Continuation 调用resume时候又重新回到这个invokeSuspend方法
// 当然我们这里由于不会返回SUSPEND_FLAG 所以继续向下运行
Object v2 = CoroutineRunKt.commonSuspendFun((Continuation)this);
if (v2 == v0) {
return v0;
}
this = this;
// 程序跳转到label_48
goto label_48;
case 1:
ResultKt.throwOnFailure(arg8);
this = this;
label_48:
this.L$0 = one;
// (四)将label设置为2
this.label = 2;
// (五) 调用挂起函数 与(三)相同
Object v3_1 = CoroutineRunKt.commonSuspendFun2((Continuation)this);
if (v3_1 == v0) {
return v0;
}
v3 = one;
arg8 = v3_1;
// (六) 走到case 2
label_60:
String two = (String)arg8;
this.L$0 = v3;
this.L$1 = two;
// (八)将label设置为3
this.label = 3;
// (九)调用最后一个挂起函数 同样与(三)一样检查结果
Object v4 = CoroutineRunKt.commonSuspendFun3((Continuation)this);
// (十)将挂起函数一、二、三返回的结果相加返回
return v4 = v0 ? v0 : v3 +two +((String)v4);
case 2:
String v2_2 = (String) this.L$0;
ResultKt.throwOnFailure(arg8);
v3 = v2_2;
this = this;
// (七) 走到label_60
goto label_60;
case 3:
Stirng v2_3 = (Stirng)this.L$1;
Stirng v1 = (Stirng)this.L$0;
ResultKt.throwOnFailure(arg8);
return v1 + v2_3 +((Stirng)arg8);
default:
throw new IllgelStateException("call to 'resume' befor 'invoke' with coroutinue");
}
}
看字节码反编译之后的代码,是不是有点头疼?我也觉得。我写了一个Java版的状态机实现Demo帮助大家理解。
代码地址:gitee.com/mcaotuman/k…
switch case表达式实现了协程的状态机模式,continuation.label是状态机的标志位,每改变一次,label加1,协程就切换1次。 一个函数如果被挂起了,它的返回值会是: CoroutineSingletons.COROUTINE_SUSPENDED
,函数恢复后会继续当前label执行下去,直到所有label执行完。
以上是关于Kotlin协程源码分析-协程的启动的主要内容,如果未能解决你的问题,请参考以下文章