完全に理解したからイケてるコードを書こうと思ったら、完全に理解してない箇所が出てきた。調べてもドンピシャに解決する記事がなかったので、自分でイケてるコードを検証したみた。
検証で使ったコード
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
class Hoge(private val context: CoroutineContext) : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = context // + Job() or + Job(context.job)
fun start() {
launch {
while(true) {
println("hoge")
delay(1_000)
}
}
}
}
suspend fun main() {
val context = Job() as CoroutineContext
val hoge = Hoge(context)
hoge.start()
val job = hoge.coroutineContext.job
context.cancel()
job.join()
}
コメントがある行(#6)をコメント通りに変更すると、Hoge#start()で実行したlaunch()の生死が変わる。
そのまま実行
Hogeクラスのコンストラクタで受け取ったCoroutineContextをそのまま使うので、start()のlaunch()で元のCoroutineContextに内包されるJobから直接子Jobが生成される。
start()では、元のCoroutineContextのJobからlaunch()したときと同じ状況であり、当然元のCoroutineContextをキャンセルすればstart()で生成されたJobは停止し、プログラムは終了する。
+ Job()
Job()の第一引数には親Jobを指定することができるが、何も指定しないと独立したJobが生成される。
また、CoroutineContextのplus()に他のCoorutineContext(Jobもこれのサブクラス)を渡すと、元のCoroutineContextの各要素がもう一方のCoroutineContextが所持している要素によって上書きされる。
そのため、+ Job()
をコードに加えると、独立した新しいJobが元のCoroutineContextが持っているJobに取って代わり、よって元のCoroutineContextがキャンセルされてもHogeで開始されたJobは動き続き、コンソールにはhoge
が繰り返し表示される。
+ Job(context.job)
親Jobを指定したため、Hoge自体のJobは子Jobであり、start()で生成されたJobは孫Jobになる。
元のCoroutineContextをキャンセルすれば、孫Jobにもキャンセルが伝播しプログラムは終了する。
まとめ
いつの間にかCoroutineContextとJobのお話になってしまいました。
適切にCoroutineScopeを分割することで、なにかいいことがある気がする。
蛇足になりますが、上記のやり方は少しだけ面倒なので、何か特別な操作を必要としない場合はclass Hoge(context: CoroutineContext) : CoroutineScope by CoroutineScope(context + Job(context.job))
に書き換えるととても便利です。