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

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

takahirom
October 24, 2018
1.4k

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

takahirom

October 24, 2018
Tweet

Transcript

  1. Job, CoroutineContext,
    CoroutineScopeなどを

    整理したい
    takahirom

    View Slide

  2. • takahirom(@new_runnable)です。
    • takahiromという名前でQiitaとかGitHubとか
    やっています。
    • 本名は⽑受 崇洋(めんじゅたかひろ)です。
    • Androidが好きです。
    • AbemaTVのAndroidアプリを作っています。
    ⾃分について

    View Slide

  3. Kotlin/kotlinx.coroutines
    ⼀時間ぐらい前に1.0.0のPRが出たみたい

    View Slide

  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 より

    View Slide

  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を実装

    View Slide

  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を作っておき

    View Slide

  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などの
    拡張関数を使ってコルーチンを
    スタート

    View Slide

  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が
    使われる

    View Slide

  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がキャンセルされる

    View Slide

  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
    これ何、、?
    何でも⾜せるの、、?

    View Slide

  11. Dispatchers.Default + job?

    View Slide

  12. Job() + Dispatchers.Default?
    Kotlinの規約(convention)を使って

    operator fun plus(..)

    で⾜し算しているのは分かるけど

    どういうことなのか探ってみました

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. CombinedContextもCoroutineContextになっている

    View Slide

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

    View Slide

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

    View Slide

  25. CoroutineContext.Elementを実装しているクラスは

    フィールドにKeyを持っている
    = JobやDispatcherはKeyを持っている

    View Slide

  26. Keyが同じものは

    上書きされる

    View Slide

  27. CoroutineContext.Element.Key

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

    companion object
    • JobはJob.Keyというcompanion object
    • 他も少しあるが上がほとんど

    View Slide

  28. val job = Job()
    val default = Dispatchers.Default
    val io = Dispatchers.IO
    val context = job + default + io
    print(context)
    defaultとio両⽅ContinuationInterceptor.Keyという
    キーなので、後から⾜されたioが残る

    View Slide

  29. CoroutineContextを操作
    する⽅法を⾒てみる

    View Slide

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

    View Slide

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

    View Slide

  32. val context = Job() + Dispatchers.Default
    CoroutineScope(context).launch {
    println(coroutineContext)
    }
    コルーチンの中で
    CoroutineContextを取り出す

    View Slide

  33. さまざまな種類の
    CoroutineContext
    https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md
    が詳しい

    View Slide

  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

    View Slide

  35. • ユーザーが新たにContextを作ったりするときに使う。Keyを
    compnaion objectで⾃分で定義する
    class AuthUser(val name: String) :
    AbstractCoroutineContextElement(AuthUser) {
    companion object Key :
    CoroutineContext.Key
    }
    coroutineContext[AuthUser]ͳͲͰऔΓग़ͤΔ
    AbstractCoroutineContextElement
    (͜Εɺɺ࢖͏ɺɺʁ)

    View Slide

  36. object Swing : AbstractCoroutineContextElement(ContinuationInterceptor),
    ContinuationInterceptor {
    override fun interceptContinuation(continuation: Continuation):
    Continuation =
    SwingContinuation(continuation)
    }
    private class SwingContinuation(val cont: Continuation) :
    Continuation {
    override val context: CoroutineContext = cont.context
    override fun resumeWith(result: Result) {
    SwingUtilities.invokeLater { cont.resumeWith(result) }
    }
    }
    • 実⾏スレッドを決めるもの(例: Dispatchers.Defaultなど)
    • キーはContinuationInterceptor.Keyを使う必要がある
    • ⾃分で実装して、任意のスレッドで⾛らせられるようにでき
    る(コードの説明は省略)
    ContinuationInterceptor

    View Slide

  37. • キーはCoroutineExceptionHandler.Keyを使う必要がある
    • Exceptionが起きたときの汎⽤的なハンドリングとして使える
    • CoroutineExceptionHandler()トップレベル関数を使うとすごく簡
    単に使える。
    val context = Job() + Dispatchers.Default +
    CoroutineExceptionHandler {
    coroutineContext, throwable -> println("catch")
    }
    CoroutineScope(context).launch {
    throw RuntimeException("!!!!!")
    }
    CoroutineExceptionHandler
    CoroutineExceptionHandlerは全体で
    Exception管理するときなどに使える!
    (Androidの場合、クラッシュも防げる)

    View Slide

  38. scope.launch(coroutineContext){}
    みたいに

    引数に渡す必要あるのかわからな
    い問題ありませんか?

    View Slide

  39. CoroutineScope.launchの中で

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

    View Slide

  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が使われる

    View Slide

  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を変えたいとかでない限りは
    渡す必要ないはず

    View Slide

  42. まとめ
    • CoroutineContextはサブクラスでいろんな事をやっているイ
    ンターフェースです
    • Keyで管理されています
    • CoroutineContextは⾜すとCombinedContextになります
    • CombinedContextの中にあるCoroutineContextはキーでいろ
    いろ操作できます
    • CoroutineExceptionHandlerはアプリ全体でException管理す
    るときなどに使えます
    • CoroutineScopeを使っていればlaunchに毎回引数で
    coroutineContextを渡す必要はないです。

    View Slide