前言👀
接触过kotlin协程的小伙伴是不是都有疑问。
- 协程是依靠什么实现的呢?
- 如何挂起?
- 如何恢复?
博主带你深度理解kotlin协程的挂起和恢复。
分析对象👀
不用协程库能不能启动协程呢?当然是可以的。
举个🌰,我们可以这么写
fun main() {
suspend {
log("coroutine start")
val value = suspendFunction1()
log(value)
log("suspendFunction----执行之后")
"1"
}.startCoroutine(object : Continuation<Any?> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
override fun resumeWith(result: Result<Any?>) {
log(result.getOrNull())
}
})
}
suspend fun suspendFunction1() = suspendCoroutine<Int> { continuation ->
val threadGroup = ThreadGroup("thread-suspend-1")
val threadExecutor = Executors.newSingleThreadExecutor {
Thread(threadGroup, it, "thread")
}
threadExecutor.submit {
Thread.sleep(2000)
continuation.resume(0)
}
}
//执行结果
09:05:44:934 [main] coroutine start
09:05:46:960 [thread] 0
09:05:46:961 [thread] suspendFunction----执行之后
09:05:46:961 [thread] 1
协程框架的launch
、async aswait
、runBlocking
等等对上述的封装,使用起来更加顺手而已。
从执行的结果来看,先输出了coroutine start,之后挂起两秒钟,继续执行之后的逻辑。
那我们就用这个demo
来分析,协程的挂起与恢复是如何实现的。😮
挂起函数分析👀
首先我们需要分析一下挂起函数是个什么玩意儿~😮
将上述suspendFunction1
挂起函数代码转换成java
看看。
public static final Object suspendFunction1(@NotNull Continuation $completion) {
...
SafeContinuation var4 = new SafeContinuation(IntrinsicsKt.intercepted($completion));
Continuation continuation = (Continuation)var4;
int var6 = false;
ThreadGroup threadGroup = new ThreadGroup("thread-suspend-1");
ExecutorService threadExecutor = Executors.newSingleThreadExecutor((ThreadFactory)(new DeepUnderstandCoroutineKt$suspendFunction1$2$threadExecutor$1(threadGroup)));
threadExecutor.submit((Runnable)(new DeepUnderstandCoroutineKt$suspendFunction1$2$1(continuation)));
Object var10000 = var4.getOrThrow();
if (var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
DebugProbesKt.probeCoroutineSuspended($completion);
}
return var10000;
}
final class DeepUnderstandCoroutineKt$suspendFunction1$2$1 implements Runnable {
// $FF: synthetic field
final Continuation $continuation;
public final void run() {
Thread.sleep(2000L);
Continuation var1 = this.$continuation;
Integer var2 = 0;
boolean var3 = false;
Companion var4 = Result.Companion;
boolean var5 = false;
var1.resumeWith(Result.constructor-impl(var2));
}
DeepUnderstandCoroutineKt$suspendFunction1$2$1(Continuation var1) {
this.$continuation = var1;
}
}
看得出,suspend函数反编译之后,会自动将一个Continuation
作为参数传递进函数,且返回类型为object
。😮
简单看一下,将Continuation
作为参数传递给了IntrinsicsKt.intercepted($completion)
。IntrinsicsKt
在java
平台的具体实现在IntrinsicsJvm
文件中。而intercepted
的源码如下所示:
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
(this as? ContinuationImpl)?.intercepted() ?: this
这里不再深追,如果您熟悉协程框架的话,这里其实返回了Continuation
的拦截器,依旧是Continuation
类型,如果不存在拦截器,其实还是返回的自身。我们上面的写法,没有拦截器,返回的就是自身。最终将返回的Continuation
传递给了SafeContinuation
。
省略掉线程池的代码,看到最后调用了SafeContinuation
的getOrThrow
函数,我们来看一下这个代码(具体实现在SafeContinuationJvm
文件):😮
internal actual class SafeContinuation<in T>
internal actual constructor(
private val delegate: Continuation<T>,
initialResult: Any?
) : Continuation<T>, CoroutineStackFrame {
@PublishedApi
internal actual constructor(delegate: Continuation<T>) : this(delegate, UNDECIDED)
...
@Volatile
private var result: Any? = initialResult
internal actual fun getOrThrow(): Any? {
var result = this.result // atomic read
if (result === UNDECIDED) {
if (RESULT.compareAndSet(this, UNDECIDED, COROUTINE_SUSPENDED)) return COROUTINE_SUSPENDED
result = this.result // reread volatile var
}
...
}
先简单看一下SafeContinuation
构造器,我们会调用它的副构造器,将**continuation
赋值给delegate
。同时赋值initialResult
为UNDECIDED
**。所以此时result == UNDECIDED
。
所以调用getOrThrow
之后,就会走if (RESULT.compareAndSet(this, UNDECIDED, COROUTINE_SUSPENDED)) return COROUTINE_SUSPENDED
。将result
设置为COROUTINE_SUSPENDED
,同时返回COROUTINE_SUSPENDED
。
所以最终suspendFunction1
函数会返回COROUTINE_SUSPENDED
标志,代表挂起。
接下来我们分析一下传递给线程执行的代码如何走到恢复呢?我们看一下run()
里面的代码,其实就是在恢复的时候,调用了SafeContinuation
的resumeWith
函数,我们看一下resumeWith
的代码:
public actual override fun resumeWith(result: Result<T>) {
while (true) { // lock-free loop
val cur = this.result // atomic read
when {
cur === UNDECIDED -> if (RESULT.compareAndSet(this, UNDECIDED, result.value)) return
cur === COROUTINE_SUSPENDED -> if (RESULT.compareAndSet(this, COROUTINE_SUSPENDED, RESUMED)) {
delegate.resumeWith(result)
return
}
else -> throw IllegalStateException("Already resumed")
}
}
}
还记得上面写道的,result
被设置为了COROUTINE_SUSPENDED
。所以此时会走delegate.resumeWith(result)
这个代码,同时将result
设置为了RESUMED
。
也就是说会通过真正的、被传进函数的原始continuation
去执行恢复操作。
好了suspend函数,就先分析到这里,接下来我们看真正的挂起和恢复如何被执行的!🙆♀️
启动分析👀
上面分析了挂起函数,这里具体看一下协程是如何启动执行的。看一下反编译后的代码:
public static final void main() {
Function1 var0 = (Function1)(new Function1((Continuation)null) {
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
Object var10000;
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
LogKt.log(new Object[]{"coroutine start"});
this.label = 1;
var10000 = DeepUnderstandCoroutineKt.suspendFunction1(this);
if (var10000 == var3) {
return var3;
}
break;
case 1:
ResultKt.throwOnFailure($result);
var10000 = $result;
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
int value = ((Number)var10000).intValue();
LogKt.log(new Object[]{Boxing.boxInt(value)});
LogKt.log(new Object[]{"suspendFunction----执行之后"});
return "1";
}
@NotNull
public final Continuation create(@NotNull Continuation completion) {
Intrinsics.checkNotNullParameter(completion, "completion");
Function1 var2 = new <anonymous constructor>(completion);
return var2;
}
public final Object invoke(Object var1) {
return ((<undefinedtype>)this.create((Continuation)var1)).invokeSuspend(Unit.INSTANCE);
}
});
boolean var1 = false;
ContinuationKt.startCoroutine(var0, (Continuation)(new Continuation() {
@NotNull
public CoroutineContext getContext() {
return (CoroutineContext)EmptyCoroutineContext.INSTANCE;
}
public void resumeWith(@NotNull Object result) {
Object[] var10000 = new Object[1];
boolean var3 = false;
var10000[0] = Result.isFailure-impl(result) ? null : result;
LogKt.log(var10000);
}
}));
}
有点长,不着急,一点一点看~🙆♀️
整体看一下,看得出suspend{}
代码块被转换为了Function1
,传入null
,又作为参数传入startCoroutine
函数用于协程的启动。startCoroutine
函数,另外接受一个Continuation
用于协程结束时进行调用。
另外我们再看一下有这么一行函数DeepUnderstandCoroutineKt.suspendFunction1(this)
,调用了我们的挂起函数,传递进去的是Funcation1
类型的。从上面分析知道,挂起函数接收的类型是Continuation
。所以Funcation1
应该是Continuation
类型的。那具体是那个类型呢?我们看一下生成的字节码,有这么一行。
final class org/example/zxf/kotlin11/DeepUnderstandCoroutineKt$main$1 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function1 {
// access flags 0x11
public final invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
所以这玩意其实是SuspendLambda
。
internal abstract class SuspendLambda(
public override val arity: Int,
completion: Continuation<Any?>?
) : ContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction {
constructor(arity: Int) : this(arity, null)
public override fun toString(): String =
if (completion == null)
Reflection.renderLambdaToString(this) // this is lambda
else
super.toString() // this is continuation
}
这玩意实现了ContinuationImpl
,ContinuationImpl
实现了BaseContinuationImpl
。这里介绍一下,后续分析需要这些类。
启动的话,调用了startCoroutine
。
public fun <T> (suspend () -> T).startCoroutine(
completion: Continuation<T>
) {
createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}
public inline fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
最后调用到了Continuation
的resumeWith
函数。
通过上面的分析我们知道,它的receiver其实是suspend{}
,也就是SuspendLambda
,它的resumeWith
函数在BaseContinuationImpl
进行了实现,我们看一下代码。
public final override fun resumeWith(result: Result<Any?>) {
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
var current = this
var param = result
while (true) {
// Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
// can precisely track what part of suspended callstack was already resumed
probeCoroutineResumed(current)
with(current) {
val completion = completion!! // fail fast when trying to resume continuation without completion
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
// unrolling recursion via loop
current = completion
param = outcome
} else {
// top-level completion reached -- invoke and return
completion.resumeWith(outcome)
return
}
}
}
}
我们代码,最后一路调用到了invokeSuspend
,这玩意不就是反编译后的invokeSuspend
嘛,所以这时候就会执行到suspend{}
代码块里面。协程启动!
挂起分析👀
调用到invokeSuspend
之后协程正式启动,为了分析方便,我把反编译之后的invokeSuspend
函数内的代码再次拉出来,如下所示:
Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
Object var10000;
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
LogKt.log(new Object[]{"coroutine start"});
this.label = 1;
var10000 = DeepUnderstandCoroutineKt.suspendFunction1(this);
if (var10000 == var3) {
return var3;
}
break;
case 1:
ResultKt.throwOnFailure($result);
var10000 = $result;
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
int value = ((Number)var10000).intValue();
LogKt.log(new Object[]{Boxing.boxInt(value)});
LogKt.log(new Object[]{"suspendFunction----执行之后"});
return "1";
接下来就是状态机的分析了,label
默认是0,所以会执行case 0
,打印coroutine start
。label
赋值为1
,调用DeepUnderstandCoroutineKt.suspendFunction1(this)
通过上面的分析我们知道,它会返回COROUTINE_SUSPENDED
标志位。var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED()
就等于COROUTINE_SUSPENDED
,此时判断相等。返回退出invokeSuspend
函数,表现为,退出了suspend{}
代码块,协程挂起。
恢复分析👀
接下来分析恢复,恢复的时候,经过上面对挂起函数的分析
我们知道,最后还是调用到了resumeWith
进而调用到了invokeSuspend
。因为这时候label == 1
,所以执行ResultKt.throwOnFailure($result)
获取到值,最后break
掉switch
。
进而执行后续写在suspend{}
的剩余代码,打印获取到的值,以及打印suspendFunction----执行之后
。然后返回了1
。
那么返回的1
,如何执行到了,startCoroutine
里面新建的continuation
了呢?调用startCoroutine
其实会调用到IntrinsicsJvm
的createCoroutineUnintercepted
,里面有这么一行代码。
create(receiver, probeCompletion)
而我们反编译suspend{}
后,看得出有create
函数,长这个样子
public final Continuation create(@NotNull Continuation completion) {
Intrinsics.checkNotNullParameter(completion, "completion");
Function1 var2 = new <anonymous constructor>(completion);
return var2;
}
所以在此时,会将completion
作为参数,传递给了SuspendLambda
。而在BaseContinuationImpl
的resumeWith
中,有这个逻辑。
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
...
} else {
// top-level completion reached -- invoke and return
completion.resumeWith(outcome)
return
}
所以,会将1
调用到completion
中去。
总结👀
kotlin协程的实现,大部分依赖编译器来着,比如悄悄的传递continuation
,状态机switch
代码的生成等等。它其实也是按顺序执行,只是根据具体挂起恢复业务不同,执行suspendInvoke
函数的分支不一样而已;
比如,我再加一个挂起函数,生成的代码就不一样了
//给suspend代码块里面新增一个挂起函数suspendFunction2
suspend {
log("coroutine start")
val value = suspendFunction1()
suspendFunction2()
log(value)
log("suspendFunction----执行之后")
"1"
}
//状态机的代码不一样了
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
LogKt.log(new Object[]{"coroutine start"});
this.label = 1;
var10000 = DeepUnderstandCoroutineKt.suspendFunction1(this);
if (var10000 == var3) {
return var3;
}
break;
case 1:
ResultKt.throwOnFailure($result);
var10000 = $result;
break;
case 2:
value = this.I$0;
ResultKt.throwOnFailure($result);
break label17;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
value = ((Number)var10000).intValue();
this.I$0 = value;
this.label = 2;
if (DeepUnderstandCoroutineKt.suspendFunction2(this) == var3) {
return var3;
}
一个协程suspend{}
在运行中,会产生常见两个continuation
,一个作为参数传递给各个挂起函数,用于恢复;另一个作为协程结束最后的回调;
另外对于挂起函数来说,如果object
返回值,如果返回的是COROUTINE_SUSPENDED
则代表挂起,返回其他则代表是正常的返回值。
相关源码实现文件列举:🙆♀️
ContinuationImpl
、IntrinsicsJvm
、SafeContinuationJvm
如果存在什么问题,欢迎指正哈。欢迎技术交流!🙆♀️
创作不易,觉得不错点个赞~🙆♀️