Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Job, CoroutineContext, CoroutineScopeなどを
整理したい

7166bc2cbc462ab5fd1987a76d0fe709?s=47 takahirom
October 24, 2018
970

Job, CoroutineContext, CoroutineScopeなどを
整理したい

7166bc2cbc462ab5fd1987a76d0fe709?s=128

takahirom

October 24, 2018
Tweet

Transcript

  1. Job, CoroutineContext, CoroutineScopeなどを
 整理したい takahirom

  2. • takahirom(@new_runnable)です。 • takahiromという名前でQiitaとかGitHubとか やっています。 • 本名は⽑受 崇洋(めんじゅたかひろ)です。 • Androidが好きです。

    • AbemaTVのAndroidアプリを作っています。 ⾃分について
  3. Kotlin/kotlinx.coroutines ⼀時間ぐらい前に1.0.0のPRが出たみたい

  4. 最近バージョンのCoroutinesで よくある記述がよくわからない import kotlin.coroutines.* import kotlinx.coroutines.* class Activity : CoroutineScope

    { lateinit var job: Job fun create() { job = Job() } fun destroy() { job.cancel() } // to be continued ... // class Activity continues override val coroutineContext: CoroutineContext get() = Dispatchers.Default + job // to be continued ... // class Activity continues • ドキュメントを読んでいるとライフサイクルの
 ハンドリングの例が出てきます https://github.com/Kotlin/kotlinx.coroutines/blob/4a821135127ba56d4e2a27d3729b3f24e5a7aad7/docs/ coroutine-context-and-dispatchers.md#coroutine-context-and-dispatchers より
  5. import kotlin.coroutines.* import kotlinx.coroutines.* class Activity : CoroutineScope { lateinit

    var job: Job fun create() { job = Job() } fun destroy() { job.cancel() } // to be continued ... // class Activity continues override val coroutineContext: CoroutineContext get() = Dispatchers.Default + job // to be continued ... // class Activity continues fun doSomething() { // launch ten coroutines for a demo, each working for a different time repeat(10) { i -> launch { delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc println("Coroutine $i is done") } } } } // class Activity ends CoroutineScopeを実装
  6. import kotlin.coroutines.* import kotlinx.coroutines.* class Activity : CoroutineScope { lateinit

    var job: Job fun create() { job = Job() } fun destroy() { job.cancel() } // to be continued ... // class Activity continues override val coroutineContext: CoroutineContext get() = Dispatchers.Default + job // to be continued ... // class Activity continues fun doSomething() { // launch ten coroutines for a demo, each working for a different time repeat(10) { i -> launch { delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc println("Coroutine $i is done") } } } } // class Activity ends onCreateとかで jobを作っておき
  7. import kotlin.coroutines.* import kotlinx.coroutines.* class Activity : CoroutineScope { lateinit

    var job: Job fun create() { job = Job() } fun destroy() { job.cancel() } // to be continued ... // class Activity continues override val coroutineContext: CoroutineContext get() = Dispatchers.Default + job // to be continued ... // class Activity continues fun doSomething() { // launch ten coroutines for a demo, each working for a different time repeat(10) { i -> launch { delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc println("Coroutine $i is done") } } } } // class Activity ends 何らかのメソッドで CoroutineScope.launchなどの 拡張関数を使ってコルーチンを スタート
  8. import kotlin.coroutines.* import kotlinx.coroutines.* class Activity : CoroutineScope { lateinit

    var job: Job fun create() { job = Job() } fun destroy() { job.cancel() } // to be continued ... // class Activity continues override val coroutineContext: CoroutineContext get() = Dispatchers.Default + job // to be continued ... // class Activity continues fun doSomething() { // launch ten coroutines for a demo, each working for a different time repeat(10) { i -> launch { delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc println("Coroutine $i is done") } } } } // class Activity ends そのときにこのCoroutineContextが 使われる
  9. import kotlin.coroutines.* import kotlinx.coroutines.* class Activity : CoroutineScope { lateinit

    var job: Job fun create() { job = Job() } fun destroy() { job.cancel() } // to be continued ... // class Activity continues override val coroutineContext: CoroutineContext get() = Dispatchers.Default + job // to be continued ... // class Activity continues fun doSomething() { // launch ten coroutines for a demo, each working for a different time repeat(10) { i -> launch { delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc println("Coroutine $i is done") } } } } // class Activity ends destory()でキャンセルすれば実⾏中 のCoroutineがキャンセルされる
  10. import kotlin.coroutines.* import kotlinx.coroutines.* class Activity : CoroutineScope { lateinit

    var job: Job fun create() { job = Job() } fun destroy() { job.cancel() } // to be continued ... // class Activity continues override val coroutineContext: CoroutineContext get() = Dispatchers.Default + job // to be continued ... // class Activity continues fun doSomething() { // launch ten coroutines for a demo, each working for a different time repeat(10) { i -> launch { delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc println("Coroutine $i is done") } } } } // class Activity ends これ何、、? 何でも⾜せるの、、?
  11. Dispatchers.Default + job?

  12. Job() + Dispatchers.Default? Kotlinの規約(convention)を使って
 operator fun plus(..)
 で⾜し算しているのは分かるけど
 どういうことなのか探ってみました

  13. plusを⾒ていく前に CoroutineContextとかが どういう関係になっているの か確認

  14. Coroutineでよく⾒るClassの関係 を確認する https://github.com/takahirom/kotlin-coroutines-class-diagram で図を公開したのでスターしてください Kotlin Coroutines 1.0.0-RC1で確認

  15. Job()で作られる インスタンスのクラス

  16. Dispatchers.Defaultで 作られるインスタンスのクラス

  17. 両⽅CoroutineContextとElementを実装している

  18. 両⽅CoroutineContextとElementを実装している

  19. CoroutineScopeは 単にCoroutineContextを持っているもの

  20. だいたいのものが CoroutineContext を実装している

  21. 実際に⾜してみる Job() + Dispatchers.Default CombinedContextができ、その中に両⽅が⼊る

  22. CombinedContextもCoroutineContextになっている

  23. もう⼀つ⾜してみる val job = Job() val default = Dispatchers.Default val

    io = Dispatchers.IO val context = job + default + io print(context)
  24. Dispatchers.Defaultが消えた? val job = Job() val default = Dispatchers.Default val

    io = Dispatchers.IO val context = job + default + io print(context)
  25. CoroutineContext.Elementを実装しているクラスは
 フィールドにKeyを持っている = JobやDispatcherはKeyを持っている

  26. Keyが同じものは
 上書きされる

  27. CoroutineContext.Element.Key
 とは • ⼤体Keyはcompanion objectのシングルトンになっている • DispatcherはContinuationInterceptor.Keyという
 companion object •

    JobはJob.Keyというcompanion object • 他も少しあるが上がほとんど
  28. val job = Job() val default = Dispatchers.Default val io

    = Dispatchers.IO val context = job + default + io print(context) defaultとio両⽅ContinuationInterceptor.Keyという キーなので、後から⾜されたioが残る
  29. CoroutineContextを操作 する⽅法を⾒てみる

  30. val context = Job() + Dispatchers.Default 追加する

  31. val dispatcher = Dispatchers.Default val context = Job() + dispatcher

    val continuationInterceptor = context[ContinuationInterceptor] println(dispatcher == continuationInterceptor) // true キーを指定して取り出す
  32. val context = Job() + Dispatchers.Default CoroutineScope(context).launch { println(coroutineContext) }

    コルーチンの中で CoroutineContextを取り出す
  33. さまざまな種類の CoroutineContext https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md が詳しい

  34. • EmptyCoroutineContext - なにもないCoroutineContext、⾜ しても何も起きない val job = Job() val

    context = job + EmptyCoroutineContext print(job == context) // true EmptyCoroutineContext • ⾃分⾃⾝をminusKey()したりすると出てくる val job = Job() val context = job.minusKey(Job.Key) print(EmptyCoroutineContext == context) // true
  35. • ユーザーが新たにContextを作ったりするときに使う。Keyを compnaion objectで⾃分で定義する class AuthUser(val name: String) : AbstractCoroutineContextElement(AuthUser)

    { companion object Key : CoroutineContext.Key<AuthUser> } coroutineContext[AuthUser]ͳͲͰऔΓग़ͤΔ AbstractCoroutineContextElement (͜Εɺɺ࢖͏ɺɺʁ)
  36. object Swing : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { override fun <T> interceptContinuation(continuation:

    Continuation<T>): Continuation<T> = SwingContinuation(continuation) } private class SwingContinuation<T>(val cont: Continuation<T>) : Continuation<T> { override val context: CoroutineContext = cont.context override fun resumeWith(result: Result<T>) { SwingUtilities.invokeLater { cont.resumeWith(result) } } } • 実⾏スレッドを決めるもの(例: Dispatchers.Defaultなど) • キーはContinuationInterceptor.Keyを使う必要がある • ⾃分で実装して、任意のスレッドで⾛らせられるようにでき る(コードの説明は省略) ContinuationInterceptor
  37. • キーはCoroutineExceptionHandler.Keyを使う必要がある • Exceptionが起きたときの汎⽤的なハンドリングとして使える • CoroutineExceptionHandler()トップレベル関数を使うとすごく簡 単に使える。 val context =

    Job() + Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable -> println("catch") } CoroutineScope(context).launch { throw RuntimeException("!!!!!") } CoroutineExceptionHandler CoroutineExceptionHandlerは全体で Exception管理するときなどに使える! (Androidの場合、クラッシュも防げる)
  38. scope.launch(coroutineContext){} みたいに
 引数に渡す必要あるのかわからな い問題ありませんか?

  39. CoroutineScope.launchの中で
 CoroutineContext.plus 使っていたので⾒てみましょう

  40. public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart =

    CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = coroutineContext + context val coroutine = StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine CoroutineScope.launchの中 (簡略化してます) 基本的にcontext = Emptyなので
 CoroutineScopeが持っているCoroutineContextが使われる
  41. public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart =

    CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = coroutineContext + context val coroutine = StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine CoroutineScope.launchの中 (簡略化してます) newContextを元にStandaloneCoroutineを⽣成している つまりDispatcherを変えたいとかでない限りは 渡す必要ないはず
  42. まとめ • CoroutineContextはサブクラスでいろんな事をやっているイ ンターフェースです • Keyで管理されています • CoroutineContextは⾜すとCombinedContextになります • CombinedContextの中にあるCoroutineContextはキーでいろ

    いろ操作できます • CoroutineExceptionHandlerはアプリ全体でException管理す るときなどに使えます • CoroutineScopeを使っていればlaunchに毎回引数で coroutineContextを渡す必要はないです。