Kotlin Coroutines の launch のコードリーディング
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
}
launch
関数は上記のようになっている。
やっていることは以下。
- 新しい CoroutineContext を作成
- コルーチンのインスタンスを作成し、開始する
-
start
がCoroutineStart.Lazy
の場合LazyStandaloneCoroutine
、そうでない場合はStandaloneCoroutine
のインスタンスを作る
-
- 戻り値の
Job
のインスタンスとして作成したコルーチンのインスタンスを返す
以下 StandaloneCoroutine
の場合について見ていく。
StandaloneCoroutine
の定義は以下。
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
...
}
AbstractCoroutine
の定義は以下。
public abstract class AbstractCoroutine<in T>(
parentContext: CoroutineContext,
initParentJob: Boolean,
active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
...
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
start(block, receiver, this)
}
}
Job
と CoroutineScope
を継承している。
launch
関数の coroutine.start(start, coroutine, block)
の部分は上記の関数が呼ばれている。
このシグネチャから R
は StandaloneCoroutine
なので、block
の型は StandaloneCoroutine.() -> T
となる。
launch
関数の第三引数の block
の型は CoroutineScope.() -> Unit
なので block
のレシーバーは StandaloneCoroutine
のインスタンスになっている。
ここから launch
でコルーチンを開始した場合に以下のことが言える。
-
launch
を呼び出すとコルーチンのインスタンスが作成される - 返り値の
Job
のインスタンスは 1 で作成したコルーチンのインスタンスと同じ -
launch
の第三引数に指定するラムダのレシーバーであるCoroutineScope
は 1 で作成したコルーチンのインスタンスとなる
AbstractCoroutine
の start
関数の処理を見る。
CoroutineStart
の invoke
メソッドが呼ばれている。
@InternalCoroutinesApi
public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
when (this) {
DEFAULT -> block.startCoroutineCancellable(receiver, completion)
ATOMIC -> block.startCoroutine(receiver, completion)
UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
LAZY -> Unit // will start lazily
}
CoroutineStart
の値によって呼び出される処理が変わっていて、ここでコルーチンが開始される模様。
LAZY
の場合は明示的に start
を呼び出さないと開始されないので、ここでは開始されないようになっている。
CoroutineStart.DEFAULT
の場合に呼ばれる block.startCoroutineCancellable(receiver, completion)
は以下のようになっている。
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
receiver: R, completion: Continuation<T>,
onCancellation: ((cause: Throwable) -> Unit)? = null
) =
runSafely(completion) {
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)
}
createCoroutineUnintercepted()
はマルチプラットフォームの実装になっていて JVM の実装は以下。
createCoroutineFromSuspendFunction
は以下のような実装になっている。
メソッド名が suspend 関数からコルーチンを作成する関数となっている。
Continuation
のインスタンスが返されているので、suspend 関数から Continuation
を作成して JVM 上で実行できる形にしているような気がする。
ちなみに Job
が子を持つようになっている。
なのでコルーチンの階層構造は実際には Job
の階層構造と言えそう。
val scope = CoroutineScope(Job())
scope.launch {
...
}
scope.launch {
}
例えば上記のようなコードの場合は、以下のような関係性になっている。
Job (= ルートのCoroutineScope が持つ Job)
/ \
Job (= StandaloneCoroutine) Job (= StandaloneCoroutine)