Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

最近バージョンの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 より

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Dispatchers.Default + job?

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

CombinedContextもCoroutineContextになっている

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

CoroutineContext.Elementを実装しているクラスは
 フィールドにKeyを持っている = JobやDispatcherはKeyを持っている

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

CoroutineContext.Element.Key
 とは • ⼤体Keyはcompanion objectのシングルトンになっている • DispatcherはContinuationInterceptor.Keyという
 companion object • JobはJob.Keyというcompanion object • 他も少しあるが上がほとんど

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

• 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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

scope.launch(coroutineContext){} みたいに
 引数に渡す必要あるのかわからな い問題ありませんか?

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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