“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情”
上一节我们介绍了kotlin协程的简单用法,以及调度器的含义 kotlin协程详解(一) 这一节我们将看一下协程的挂起与恢复,以及解释上一章中,为什么使用Dispatchers.Unconfined调度器后,代码会运行在kotlin默认的线程池中。
kotlin的协程本质上是方法的return和回调,这部分是编译器帮我们实现的。
被suspend关键字修饰的方法在编译阶段,编译器会修改方法的签名,包括返回值,修饰符,入参,方法体实现。 下图是协程回调的过程。
object Test {
private suspend fun request1() : String{
delay(2 * 1000)
TLog.dt("TAG","request1")
return "request1"
}
}
上面是 kotlin 写的协程代码,经过编译器编译后,会生成如下的 java 代码
public final class Test {
@NotNull
public static final Test INSTANCE = new Test();
public static final int $stable;
private Test() {
}
private final Object request1(Continuation $completion) {
Object $continuation;
label20: {
if ($completion instanceof <undefinedtype>) {
$continuation = (<undefinedtype>)$completion;
if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
break label20;
}
}
$continuation = new ContinuationImpl($completion) {
// $FF: synthetic field
Object result;
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return Test.this.request1((Continuation)this);
}
};
}
Object $result = ((<undefinedtype>)$continuation).result;
Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (((<undefinedtype>)$continuation).label) {
case 0:
ResultKt.throwOnFailure($result);
((<undefinedtype>)$continuation).label = 1;
if (DelayKt.delay(2000L, (Continuation)$continuation) == var5) {
return var5;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
TLog var10000 = TLog.INSTANCE;
Object[] var2 = new Object[]{"request1"};
TLog.dt$default(var10000, "TAG", var2, false, false, 12, (Object)null);
return "request1";
}
}
可以看到,经过 kotlin 编译器后request1 方法被添加了一个Continuation 和 Object 返回值。
该方法的 10 行~31 行是在检测Continuation是否已经被包装成 ContinuationImpl类型的参数。
其中我们重点关注invokeSuspend 方法。他会在被回调的时候在去调用 request1 方法从而恢复方法的运行。
- 进入方法后,会进入到 swicth 语句中, 由于continuation是第一次创建, label 为 0。所以会进入 case 0 的分支中。
- 这里会进入到DelayKt.delay 方法中
@Nullable
public static final Object delay(long timeMillis, @NotNull Continuation $completion) {
if (timeMillis <= 0L) {
return Unit.INSTANCE;
} else {
int $i$f$suspendCancellableCoroutine = false;
Continuation uCont$iv = $completion;
int var5 = false;
CancellableContinuationImpl cancellable$iv = new CancellableContinuationImpl(IntrinsicsKt.intercepted(uCont$iv), 1);
cancellable$iv.initCancellability();
CancellableContinuation cont = (CancellableContinuation)cancellable$iv;
int var8 = false;
if (timeMillis < Long.MAX_VALUE) {
getDelay(cont.getContext()).scheduleResumeAfterDelay(timeMillis, cont);
}
Object var10000 = cancellable$iv.getResult();
if (var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
DebugProbesKt.probeCoroutineSuspended($completion);
}
return var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;
}
}
在这个方法中主要做了 3 件事
- 1~11 行将传递进来的Continuation包装成CancellableContinuation类型
- 14 行将CancellableContinuation传入scheduleResumeAfterDelay方法中,这个方法就是在子线程中延迟给定的秒数后执行传进来的回调
- 接着 17 行会同步调用 getResult 在CancellableContinuationImpl中 getResult 方法会首先调用 trySuspend 方法,验证当前方法的状态,默认是UNDECIDED,导致 if 语句为 true 进入 if 语句中, 从而返回COROUTINE_SUSPENDED。
internal fun getResult(): Any? {
val isReusable = isReusable()
// trySuspend may fail either if 'block' has resumed/cancelled a continuation
// or we got async cancellation from parent.
if (trySuspend()) {
/*
* Invariant: parentHandle is `null` *only* for reusable continuations.
* We were neither resumed nor cancelled, time to suspend.
* But first we have to install parent cancellation handle (if we didn't yet),
* so CC could be properly resumed on parent cancellation.
*
* This read has benign data-race with write of 'NonDisposableHandle'
* in 'detachChildIfNotReusable'.
*/
if (parentHandle == null) {
installParentHandle()
}
/*
* Release the continuation after installing the handle (if needed).
* If we were successful, then do nothing, it's ok to reuse the instance now.
* Otherwise, dispose the handle by ourselves.
*/
if (isReusable) {
releaseClaimedReusableContinuation()
}
return COROUTINE_SUSPENDED
}
// otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state
if (isReusable) {
// release claimed reusable continuation for the future reuse
releaseClaimedReusableContinuation()
}
val state = this.state
if (state is CompletedExceptionally) throw recoverStackTrace(state.cause, this)
// if the parent job was already cancelled, then throw the corresponding cancellation exception
// otherwise, there is a race if suspendCancellableCoroutine { cont -> ... } does cont.resume(...)
// before the block returns. This getResult would return a result as opposed to cancellation
// exception that should have happened if the continuation is dispatched for execution later.
if (resumeMode.isCancellableMode) {
val job = context[Job]
if (job != null && !job.isActive) {
val cause = job.getCancellationException()
cancelCompletedResult(state, cause)
throw recoverStackTrace(cause, this)
}
}
return getSuccessfulResult(state)
}
4. 因为 getResult 返回COROUTINE_SUSPENDED, 导致 Delaykt.delay 方法直接返回。从而使整个方法被 return,导致协成被挂起。这时候我们用 kotlin 编写的代码,最后两句日志代码将不会被打印。
5. 在经过 2s 的延时后,getDelay(cont.getContext()).scheduleResumeAfterDelay(timeMillis, cont);
方法中的回调被调用, 该方法会调用BaseContinuationImpl中的public final void resumeWith(@NotNull Object result)方法
public final void resumeWith(@NotNull Object result) {
Object current = null;
current = this;
Object param = null;
param = result;
while(true) {
DebugProbesKt.probeCoroutineResumed((Continuation)current);
BaseContinuationImpl $this$resumeWith_u24lambda_u240 = (BaseContinuationImpl)current;
int var6 = false;
Continuation var10000 = $this$resumeWith_u24lambda_u240.completion;
Intrinsics.checkNotNull(var10000);
Continuation completion = var10000;
Object outcome;
Result.Companion var12;
try {
outcome = $this$resumeWith_u24lambda_u240.invokeSuspend(param);
if (outcome == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
return;
}
var12 = Result.Companion;
outcome = Result.constructor-impl(outcome);
} catch (Throwable var11) {
Throwable exception = var11;
var12 = Result.Companion;
outcome = Result.constructor-impl(ResultKt.createFailure(exception));
}
Object outcome = outcome;
$this$resumeWith_u24lambda_u240.releaseIntercepted();
if (!(completion instanceof BaseContinuationImpl)) {
completion.resumeWith(outcome);
return;
}
current = completion;
param = outcome;
}
}
6. 在上面的方法中,18 行会调用我们的协程方法中的 invokeSuspend方法,在invokeSuspend方法中,我们可以看到,会回调 request1 方法本身,最终kotlin编写的协程代码最后的两行日志将会被打印,这就是协成如何恢复的。 7. 如果返回值还是COROUTINE_SUSPENDED,那么说明上层的方法在挂起状态,直接 return。在来到 34 行。这里调用completion.resumeWith(outcome),resumeWith 方法会逐层调用上层传递的回调,也就是上层方法的本身,最终将结果返回出去。