这个问题的目的是了解挂起方法在底层是如何工作的,以及 IntrinsicsKt.getCOROUTINE_SUSPENDED();
的意义是什么?
在下面的字节码中你会看到一段代码
if (var10000.resultingBlockingOp-IoAF18A((Continuation)$continuation) == var4) {
return var4;
}
其中 var4 是
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
如果函数返回,那么协程将如何执行?
在它调用自身的 invokeSuspend
方法中,这是否意味着挂起方法通过底层递归来工作?
class CoroutineExplanatoryUseCase {
private val coroutineExplanatoryRepo: CoroutineExplanatoryRepo = CoroutineExplanatoryRepo()
suspend fun simpleOp() {
coroutineExplanatoryRepo.simpleBlockingOp()
}
suspend fun resultingBlockingOp() {
coroutineExplanatoryRepo.resultingBlockingOp()
}
}
生成的字节码
public final class CoroutineExplanatoryUseCase {
private final CoroutineExplanatoryRepo coroutineExplanatoryRepo = new CoroutineExplanatoryRepo();
@Nullable
public final Object simpleOp(@NotNull Continuation $completion) {
Object var10000 = this.coroutineExplanatoryRepo.simpleBlockingOp($completion);
return var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;
}
@Nullable
public final Object resultingBlockingOp(@NotNull Continuation var1) {
Object $continuation;
label20: {
if (var1 instanceof <undefinedtype>) {
$continuation = (<undefinedtype>)var1;
if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
break label20;
}
}
$continuation = new ContinuationImpl(var1) {
// $FF: synthetic field
Object result;
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return CoroutineExplanatoryUseCase.this.resultingBlockingOp(this);
}
};
}
Object $result = ((<undefinedtype>)$continuation).result;
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (((<undefinedtype>)$continuation).label) {
case 0:
ResultKt.throwOnFailure($result);
CoroutineExplanatoryRepo var10000 = this.coroutineExplanatoryRepo;
((<undefinedtype>)$continuation).label = 1;
if (var10000.resultingBlockingOp-IoAF18A((Continuation)$continuation) == var4) {
return var4;
}
break;
case 1:
ResultKt.throwOnFailure($result);
((Result)$result).unbox-impl();
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return Unit.INSTANCE;
}
}
注意:我已经知道当延续 C 恢复时,它将恢复延续 B,然后它将恢复 A,我想要理解的一点是在值与 IntrinsicsKt.getCOROUTINE_SUSPENDED()
匹配之后有一些 return 语句,如果该函数返回,那么它将最终如何执行 switch/if
中的其余分支。
所以如果你能解释一下逐步的控制流程,那就太好了!!!
最佳答案
详细回答暂停如何工作的问题并不容易,因为过程相当复杂。正如评论中所述,您可以阅读 a blog post by Marcin Moskała或者 my another answer .
但我们可以关注您的主要问题,即 IntrinsicsKt.getCOROUTINE_SUSPENDED()
的含义是什么,为什么协程从函数返回以及返回后它们如何继续。
问题
实现协程的最大挑战之一是使暂停成为可能。 挂起是指在函数内等待而不占用任何线程。我们需要意识到,这在 JVM、JavaScript 和大多数其他运行时中在技术上是不可能的。这些运行时根本不支持此类功能,我们无法像这样添加此类支持。
解决方案
Kotlin 作者介绍了一个解决方案,它几乎是 JVM/JS 之上的黑客/解决方法。这个想法是:如果我们无法在函数内部释放线程,那么......让我们返回。稍后我们可以再次调用该函数并直接跳转到我们所在的位置。
这本质上就是 Kotlin 中暂停的工作原理。这个复杂字节码的全部要点是允许在每个挂起点跳出(返回)函数并跳回每个挂起点。挂起点是代码中我们可能需要挂起的位置 - 实际上,它们是对其他挂起函数的调用。此外,我们使用延续来存储本地状态:代码偏移量和本地变量。
流程简单如下:
- 我们调用一个挂起函数
a
,它调用b
,后者又调用c
。我们的调用堆栈是:a -> b -> c
c
挂起,因此它将其状态存储在其延续中,并以IntrinsicsKt.getCOROUTINE_SUSPENDED()
返回。- 为了释放线程,我们不仅要从
c
返回,还要从整个调用堆栈返回。b
验证c
返回IntrinsicsKt.getCOROUTINE_SUSPENDED()
并且它也返回相同的值。a
执行相同的操作,因此现在线程可以自由地执行其他操作。 - 当我们需要恢复时,我们通过传递其延续来调用
c
(无需重建调用堆栈)。 c
读取其延续,跳转到正确的偏移量,加载其局部变量并照常继续。- 在
c
返回后,我们对b
执行相同的操作,然后对a
执行相同的操作。每个延续都保留调用者的延续,因此我们始终拥有所有延续的链。它的作用与调用堆栈类似。
所以这个复杂的字节码实际上是被切成碎片的函数代码。每个悬挂点都是一个切口。我们需要一种方法来从每个部分跳出和跳入。
最终想法
我建议阅读反编译代码时要小心。反编译器尝试猜测产生特定字节码的原始源代码是什么。但问题是:挂起函数的字节码不是编译常规 Java/Kotlin 代码的结果。甚至不可能编写与分析的字节码等效的源代码。因此,反编译器可能会感到困惑并生成并不真正正确的代码。如果您对该语言的如此深入的细节感兴趣,那么我认为您应该尝试直接读取字节码。
关于kotlin - kotlin 中的挂起函数/方法是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76936370/