🏎️

Coroutine context and dispatchersをよんでまとめる ~その1~

2024/02/22に公開

はじめに

CoroutineContextやらDispatcherを雰囲気で使っていたのでCoroutine context and dispatchersを読んでまとめてみました。
ほぼ翻訳しただけみたいなところもありますが、ご了承ください。

Coroutine context and dispatchers

コルーチンは常に、Kotlin標準ライブラリで定義されているCoroutineContext型で実行されます。
CoroutineContextは様々な要素の集合であり、主な要素は、JobとDispatcherです。
coroutine contextについて

Dispatcher and thread

CoroutineContextには、CoroutineDispatcherが含まれておりコルーチンの実行を特定のスレッドに限定したり、スレッドプールにディスパッチしたり、限定せずに実行させたりすることができます。

launchやasyncのような全てのコルーチンビルダーは、オプションのCoroutineContextパラメータを受け付けます。
このCoroutineContextパラメータは、新しいコルーチンのディスパッチャやその他のコンテキスト要素を明示的に指定するために使用できます。

launch {
    println("runBlocking内で実行 ${Thread.currentThread().name}")
    // main @coroutine#2
}
launch(Dispatchers.Unconfined) {
    println("Unconfined ${Thread.currentThread().name}")
    // main @coroutine#3
}
launch(Dispatchers.Default) {
    println("Default  ${Thread.currentThread().name}")
    // DefaultDispatcher-worker-1 @coroutine#4
}
launch(newSingleThreadContext("MyOwnThread")) {
    println("newSingleThreadContext ${Thread.currentThread().name}")
    // MyOwnThread @coroutine#5
}

launch {...}

パラメータなしで使用すると、起動元のCoroutineScopeのコンテキストのディスパッチャを継承します。
この場合、メインスレッドで実行されるメインのrunBlockingコルーチンのコンテキストが適応されます。

Dispatchers.Unconfined

メインスレッドで実行されるように見える特殊なディスパッチャーらしい。

Dispatchers.Default

スコープ内で他のディスパッチャーが明示的に指定されていない場合に使用され、
スレッドの共有バックグラウンドプールを使用します。

newSingleThreadContext

コルーチンが実行するスレッドを作成します。専用のスレッドは非常に高価なリソースです。
実際のアプリケーションでは、不要になったらclose関数を使って解放するか、トップレベルの変数に格納してアプリケーション全体で再利用する必要があります。

Unconfined vs confined dispatcher

Dispatchers.Unconfinedは、呼び出し元のスレッドでコルーチンを開始しますが最初のサスペンドポイントまでです。

サスペンド後は、呼び出されたサスペンド関数によって決定されたスレッドでコルーチンを再開します。
CPU時間を消費したり、特定のスレッドに限定された共有データ(UIなど)を更新したりしないコルーチンに適しています。

一方、デフォルトのディスパッチャーでは外側のCoroutineScopeから継承されます。
runBlockingコルーチンのデフォルトのディスパッチャーは呼び出し元のスレッドに限定されているため、これを継承することで、処理の実行をこのスレッドに限定する効果があります。

launch(Dispatchers.Unconfined) {
    println("Unconfined: start ${Thread.currentThread().name}")
    delay(500)
    println("Unconfined: after delay ${Thread.currentThread().name}")
}
launch {
    println("main runBlocking: start ${Thread.currentThread().name}")
    delay(1000)
    println("main runBlocking: after delay ${Thread.currentThread().name}")
}
Unconfined      : I'm working in thread main @coroutine#2
main runBlocking: I'm working in thread main @coroutine#3
Unconfined      : After delay in thread kotlinx.coroutines.DefaultExecutor @coroutine#2
main runBlocking: After delay in thread main @coroutine#3

上記の通り、Unconfinedでは最初のサスペンド(delay)のあとに実行スレッドが変更されており、
この場合は、delayが使用しているスレッドで実行されます。

まとめ

Dispatchers.Unconfinedについては使い所がとても難しそう。
ドキュメントにも難しい技術なので基本使わない方がいい的なことが書かれていたので不用意についかわない方が良さそう.....

公式ドキュメントは読めば読むほど疑問が出てきて大変ですね。
CoroutineContextも実装を見てみましたがなんのこっちゃって感じでした
頑張っていきたい

株式会社ガラパゴス(有志)

Discussion