Coroutine Suspending Functionsを理解したい2
続きの記事です。 Coroutine Suspending Functionsを理解したい1 - sky’s 雑記
振り返り
fun main(args: Array<String>) { CoroutineScope(Dispatchers.Main).launch { println("start") greet() println("end") } } suspend fun greet() { println("suspended!!") }
上記コードをByteコードへCompileするとgreetの引数に Continuation
という引数が渡ったコードが生成されることを見ました。
またCompileしたコードをDecompileすると以下のようなコードが生成されました、今回はDecompileしたコードからsuspendの実体を調べていきたいと思います。
... public static final void main(@NotNull String[] args) { Intrinsics.checkParameterIsNotNull(args, "args"); BuildersKt.launch$default(CoroutineScopeKt.CoroutineScope((CoroutineContext)Dispatchers.getMain()), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) { private CoroutineScope p$; Object L$0; int label; @Nullable public final Object invokeSuspend(@NotNull Object $result) { Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); CoroutineScope $this$launch; String var3; boolean var4; switch(this.label) { case 0: ResultKt.throwOnFailure($result); $this$launch = this.p$; var3 = "start"; var4 = false; System.out.println(var3); this.L$0 = $this$launch; this.label = 1; if (TestKt.greet(this) == var5) { return var5; } break; case 1: $this$launch = (CoroutineScope)this.L$0; ResultKt.throwOnFailure($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } var3 = "end"; var4 = false; System.out.println(var3); return Unit.INSTANCE; } @NotNull public final Continuation create(@Nullable Object value, @NotNull Continuation completion) { Intrinsics.checkParameterIsNotNull(completion, "completion"); Function2 var3 = new Function2(completion) { private CoroutineScope p$; Object L$0; int label; @Nullable public final Object invokeSuspend(@NotNull Object $result) {
CoroutineScope.launch(Java)
はじめにCoroutineの起動から見ていきます。 Decompileされた以下のコードは
BuildersKt.launch$default(CoroutineScopeKt.CoroutineScope((CoroutineContext)Dispatchers.getMain()), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
Kotlinで以下のように実装されています。
public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }
greet()を含むCoroutineScopeに渡されるラムダ式は block: suspend CoroutineScope.() -> Unit
にあたり new Function2((Continuation)null)
に変換されます。Kotlinにおけるラムダ式はFunction2(2とは限らない)というインターフェースを実装したクラスのようで(1)、Coroutineで何が行われるかを理解するには続くFunction2の実装を追いかければ良さそうです。
CoroutineScope.launch(Kotlin)
Javaのコードを追いかけたいところですが、ここで一旦Kotlinのほうのソースコードを眺めてCoroutineの理解を深めたいと思います。 CoroutineScope.launchで渡されたblock(ラムダ式)は以下のように実行されます。CoroutineScopeで実行されるblock全体もsuspendableです。
- Builders.common.kt
public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine }
- AbstractCoroutine.kt
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) { initParentJob() start(block, receiver, this) }
- CoroutineStart.kt
@InternalCoroutinesApi public operator fun <T> invoke(block: suspend () -> T, completion: Continuation<T>) = when (this) { CoroutineStart.DEFAULT -> block.startCoroutineCancellable(completion) CoroutineStart.ATOMIC -> block.startCoroutine(completion) CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(completion) CoroutineStart.LAZY -> Unit // will start lazily }
CoroutineScope.launchにおいて LazyStandaloneCoroutine
or StandaloneCoroutine
を生成します。いずれもAbstractCoroutineを実装しておりAbstractCoroutineは以下のようにContinuationを継承します。
- AbstractCoroutine.kt
@InternalCoroutinesApi public abstract class AbstractCoroutine<in T>( /** * The context of the parent coroutine. */ @JvmField protected val parentContext: CoroutineContext, active: Boolean = true ) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
CoroutineStartのstartによりblockが実行されるようですが、第2引数としてContinuationを受け取りこれはCoroutineScope.launchにおいて生成したCoroutineそのものです。CoroutineScopeにおいて生成したCoroutine(Continuation)が実行時に伝搬していく様子が徐々に見て取れるようになってきました。
前回の記事で触れた suspend関数は継続渡し方式 (Continuation Passing Style)で実装されている
という話が少し実感できるようになったところでここまでにします。
少し脱線しましたが次回はJavaコードに戻りinvokeSuspend/createあたりを深堀りしてsuspend関数がどのように処理を中断するか調べていきます。