Slide 1

Slide 1 text

Understanding Kotlin Coroutines @mhidaka 1 コルーチンで進化するアプリケーション開発

Slide 2

Slide 2 text

資料正誤表 2 発表時に誤りがあった場所を次の通り修正しています。 P17:スレッドと継続の誤った理解・表記を削除 P70, 73:並行性(誤変換) P21, 36, 65:Coroutine (typo) P52 : Fragment (typo) P73:Session Keyword (typo)

Slide 3

Slide 3 text

@mhidaka 3 TechBooster 技術書典

Slide 4

Slide 4 text

PEAKS 4

Slide 5

Slide 5 text

メインポイント / Main points 5

Slide 6

Slide 6 text

今日、伝えたいこと 6 ・How to use Kotlin Coroutines ・Apply Coroutines to Android App ・When should I use Coroutines

Slide 7

Slide 7 text

はじめに: コルーチン登場の背景 The first step is always the hardest. 7

Slide 8

Slide 8 text

コルーチンとは 8 Coroutines are light-weight threads Coroutines are new way of managing background threads

Slide 9

Slide 9 text

はじめてのコルーチン 9 import kotlinx.coroutines.* fun main() { GlobalScope.launch { // コルーチンの起動 delay(1000L) // コルーチンを1秒中断 println(“World!”) // “World” を出力 } println(“Hello,”) // メインスレッドで “Hello,” を出力 Thread.sleep(2000L) // スレッドを2秒停止(JVMが終了しないように) } https://kotlinlang.org/docs/reference/coroutines/basics.html

Slide 10

Slide 10 text

はじめてのコルーチンと出力 10 import kotlinx.coroutines.* fun main() { GlobalScope.launch { // コルーチンの起動 delay(1000L) // コルーチンを1秒中断 println(“World!”) // “World” を出力 } println(“Hello,”) // メインスレッドで “Hello,” を出力 Thread.sleep(2000L) // スレッドを2秒停止(JVMが終了しないように) } https://kotlinlang.org/docs/reference/coroutines/basics.html $ > Hello, World!

Slide 11

Slide 11 text

コルーチンの特徴 11 Suspending Coroutines, Blocking Threads CC BY SA 2.0 https://www.flickr.com/photos/librariesrock/44436145281/

Slide 12

Slide 12 text

コルーチンの特徴:中断可能 12 ひとくくりの処理に対して中断と再開を提供 Coroutine Task Job A Job B Job C Entry Return Job A~Cに対して処理の中断/再開を提供する (スレッドをブロックしない!)

Slide 13

Slide 13 text

サスペンド関数 - Suspending Functions 13 GlobalScope.launch { // コルーチンの起動 delay(1000L) // コルーチンを1秒中断 println(“World!”) // “World” を出力 } suspend fun delay(timeMillis: Long): Unit (source) https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html

Slide 14

Slide 14 text

サスペンド関数 - Suspending Functions 14 ・コルーチンはサスペンド関数のタイミングでのみ中断 ・書いた処理を順次に評価して実行する点は変わらない ・launchブロックが中断可能な関数と通常のコードをつなぐ suspend fun delay(timeMillis: Long): Unit (source)

Slide 15

Slide 15 text

コルーチンの特徴(イメージ) 15 複数のコルーチンで協調し、資源を有効に活用 Coroutine1の関数が分割されて実行している点に注意 Coroutine 1 Job A Job B Job F Coroutine 2 Job C Job D Job E Coroutine 1 Coroutine 3 CPU等利用の時間軸 Suspending Functions Coroutines

Slide 16

Slide 16 text

コルーチンの特徴(イメージ) 16 複数のコルーチンで協調し、資源を有効に活用 Coroutine1の関数が分割されて実行している点に注意 Coroutine 1 Job A Job B Job F Coroutine 2 Job C Job D Job E Coroutine 1 Coroutine 3 CPU等利用の時間軸 Suspending Functions Coroutines 関数の実行を中断できる

Slide 17

Slide 17 text

継続 - Continuation 17 前の状態を引き継ぐこと。 コンピュータサイエンスではプログラムの実行途 中に、まだ評価していない残りのプログラムや処 理を完了するための手続きや仕組みを指す。

Slide 18

Slide 18 text

memo 18 スレッドも継続の実装形態のひとつ。 サブルーチンにはコルーチンのような中 断はないため、必ず終了しなければ次の 関数は呼び出せません。マルチスレッド プログラミングによって協調動作し、並 行性を確保している。 スレッドの停止/同期処理はプログラマ の責務。 2019/2/11 スライドに誤った理解に基づく誤記がありましたので訂正しています。 継続の理解には次の資料がおすすめです http://practical-scheme.net/docs/cont-j.html

Slide 19

Slide 19 text

コールバックヘルの解決(擬似コード) 19 callbackA ( a -> { callbackB ( b -> { callbackC ( c -> { sum = a + b + c } ) }) } ) launch { // コルーチンの起動 val a = callbackA() val b = callbackB() val c = callbackC() val sum = a + b + c }

Slide 20

Slide 20 text

20 Cancellation CC BY-SA 2.0 https://www.flickr.com/photos/mattx27/5233966181/

Slide 21

Slide 21 text

コルーチンの中止 - Cancellation 21 ・コルーチンを実行する箱庭:コルーチンスコープ ・中止にはコルーチンスコープの提供するJobが必要 ・launch関数はコルーチンを作るビルダー https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html

Slide 22

Slide 22 text

コルーチンスコープ - CoroutineScope 22 GlobalScope.launch { // コルーチンの起動 delay(1000L) // コルーチンを1秒中断 println(“World!”) // “World” を出力 } object GlobalScope : CoroutineScope { ...} https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html

Slide 23

Slide 23 text

コルーチンスコープ - CoroutinScope 23 GlobalScope.launch { // コルーチンの起動 delay(1000L) // コルーチンを1秒中断 println(“World!”) // “World” を出力 } object GlobalScope : CoroutineScope { ...} https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html Job A Job B

Slide 24

Slide 24 text

memo 24 アプリケーション開発においては 通常、GrobalScopeは利用しませ ん。アプリ内のライフサイクルに 基づいてCoroutineScopeを独自定 義することを強く推奨しています。

Slide 25

Slide 25 text

キャンセル処理の例 25 https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html val job = CoroutineScope(Dispatchers.Default).launch{ repeat(1000) { i -> println(“I‘m sleeping $i ...”) delay(500L) } } delay(1300L) // 1.3秒中断する println(“main: I’m tired of waiting!”) job.cancel() // ジョブの中止 job.join() // 中止処理の完了待ち println("main: Now I can quit.")

Slide 26

Slide 26 text

キャンセル処理の例 26 https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html val job = CoroutineScope(Dispatchers.Default).launch{ repeat(1000) { i -> println(“I‘m sleeping $i ...”) delay(500L) } } delay(1300L) // 1.3秒中断する println(“main: I’m tired of waiting!”) job.cancel() // ジョブの中止 job.join() // 中止処理の完了待ち println("main: Now I can quit.") 実行単位・スコープ コルーチンの作成 バックグラウンド・ジョブ

Slide 27

Slide 27 text

キャンセル処理の例 27 https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html val job = CoroutineScope(Dispatchers.Default).launch{ repeat(1000) { i -> println(“I‘m sleeping $i ...”) delay(500L) } } delay(1300L) // 1.3秒中断する println(“main: I’m tired of waiting!”) job.cancel() // ジョブの中止 job.join() // 中止処理の完了待ち println("main: Now I can quit.")

Slide 28

Slide 28 text

28 Concurrency (and Parallels) CC BY-SA 2.0 https://www.flickr.com/photos/bobellaphotography/8494268626/

Slide 29

Slide 29 text

並行性と並列性 – Concurrency and Parallels 29 並行性: プログラムや問題解決において順序に依存しない、部 分的なコンポーネントに分解してリソースを最大限活 用(問題を解決)する構成のこと 並列性: 同時に複数の処理を実行(Execution)すること

Slide 30

Slide 30 text

並行性と並列性 – Concurrency and Parallels 30 並行性 並列性 CPU1 CPU2 Coroutine2 Coroutine1

Slide 31

Slide 31 text

並行性と並列性 – Concurrency and Parallels 31 https://talks.golang.org/2012/waza.slide

Slide 32

Slide 32 text

並行性と並列性 – Concurrency and Parallels 32

Slide 33

Slide 33 text

並行性と並列性 – Concurrency and Parallels 33

Slide 34

Slide 34 text

並行性と並列性 – Concurrency and Parallels 34

Slide 35

Slide 35 text

並行性と並列性 – Concurrency and Parallels 35

Slide 36

Slide 36 text

並行性と並列性 – Concurrency and Parallels 36

Slide 37

Slide 37 text

構造化コンカレンシー / Structured Concurrency 37 複数のコルーチンの並行処理 / 制御を手助けする構成 バージョン0.26.0でCoroutineScopeを導入 CoroutineScope A CorutineScope B Coroutine … Coroutine …

Slide 38

Slide 38 text

コルーチンは順次処理が基本 38 suspend fun loadFromNetwork(endPoint1: String, endPoint2: String): ResultData { val result1 = callApi(endPoint1) val result2 = callApi(endPoint2, result1) // 最初の結果を使う return combineResults(result1, result2) // 結果をマージして返却 } suspend fun callApi(endPoint: String): ResultData { … } // ネットワーク処理

Slide 39

Slide 39 text

asyncでコルーチン化し待ち合わせる(1/2) 39 suspend fun loadFromNetwork(endPoint1: String, endPoint2: String): ResultData { val deferred1 = async { callApi(endPoint1) } val deferred2 = async { callApi(endPoint2) } combineResults(deferred1.await(), deferred2.await()) } https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html

Slide 40

Slide 40 text

asyncでコルーチン化し待ち合わせる(1/2) 40 suspend fun loadFromNetwork(endPoint1: String, endPoint2: String): ResultData { val deferred1 = async { callApi(endPoint1) } val deferred2 = async { callApi(endPoint2) } combineResults(deferred1.await(), deferred2.await()) } https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html fun CoroutineScope.async( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): Deferred (source)

Slide 41

Slide 41 text

CoroutineScopeによる構造化(2/2) 41 suspend fun loadFromNetwork(endPoint1: String, endPoint2: String): ResultData = coroutineScope { val deferred1 = async { callApi(endPoint1) } val deferred2 = async { callApi(endPoint2) } combineResults(deferred1.await(), deferred2.await()) } https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html interface Deferred : Job (source) abstract suspend fun await(): T

Slide 42

Slide 42 text

42 コルーチンは標準で上から下への順 次処理です。並行処理したい場合に async/awaitで待ち合わせます。これ は並行処理の概念が難しく、joinを書 き損じたときなどには複雑な不具合 が発生するための処置です。 余談:C#は並行処理がデフォルトの ため”Classic way“と呼んだことも。 CoroutineScope

Slide 43

Slide 43 text

現代のアプリケーション開発 の課題 Introduction of application problem to be solved 43

Slide 44

Slide 44 text

すばやく、品質よく、価値のあるものを。 44 直面する課題 ・規模が大きくなるアプリケーション開発 ・開発の効率化と高い拡張性が求められる ・ライブラリ利用による初期コスト

Slide 45

Slide 45 text

45 メインスレッドを止めるな! CC BY-SA 2.0 https://www.flickr.com/photos/ooocha/2849172733/

Slide 46

Slide 46 text

メインスレッドへの保護 46 良いUXを提供するためには通信、処理時間がかか るものをバックグラウンドタスクにしてUIスレッ ドをブロッキングしない構造が求められる

Slide 47

Slide 47 text

独立した複数のモジュールが連動して動作 47 関心の分離(Separation of concerns) 単一責務の原則(Single Responsibility Principle) これらにもとづいて設計されたモジュールが有機 的に連携する

Slide 48

Slide 48 text

期待されるコルーチンの役割 48

Slide 49

Slide 49 text

コルーチンの特徴(復習) 49 中断/再開によるリソース保護 コルーチンスコープによる明確な構造化 高効率な並行性の獲得

Slide 50

Slide 50 text

典型的なアプリ構造の課題設定 50 DataSource Repository ViewModel State Operations UI / View Activity Fragment モジュール間でのシーケンスを考える

Slide 51

Slide 51 text

アプリケーションへの適用 51

Slide 52

Slide 52 text

Android-sunflower 52 https://github.com/googlesamples/android-sunflower

Slide 53

Slide 53 text

Android-sunflower 53 https://github.com/googlesamples/android-sunflower Fragument ViewModel Repository Database

Slide 54

Slide 54 text

MVVMアーキテクチャで実践 するCoroutines 54

Slide 55

Slide 55 text

UI 55 https://github.com/googlesamples/android-sunflower class PlantDetailFragment : Fragment() { override fun onCreateView(...): View? { ... val binding = DataBindingUtil.inflate( inflater, R.layout.fragment_plant_detail, container, false).apply { viewModel = plantDetailViewModel setLifecycleOwner(this@PlantDetailFragment) fab.setOnClickListener { view -> plantDetailViewModel.addPlantToGarden() Snackbar.make(view, R.string.added_plant_to_garden, Snackbar.LENGTH_LONG).show() } } ... } ... } Fragment ViewModel Repository Database fab.setOnClickListener { view -> plantDetailViewModel.addPlantToGarden() Snackbar.make(view, R.string.added_plant_to_garden, Snackbar.LENGTH_LONG).show() }

Slide 56

Slide 56 text

ViewModel – Launch Coroutine 56 class PlantDetailViewModel(...) : ViewModel() { ... fun addPlantToGarden() { viewModelScope.launch { gardenPlantingRepository.createGardenPlanting(plantId) } } } Fragment ViewModel Repository Database

Slide 57

Slide 57 text

ViewModel - Create CoroutineScope 57 class PlantDetailViewModel(...) : ViewModel() { private val viewModelJob = Job() private val viewModelScope = CoroutineScope(Main + viewModelJob) override fun onCleared() { super.onCleared() viewModelJob.cancel() } fun addPlantToGarden() { viewModelScope.launch { gardenPlantingRepository.createGardenPlanting(plantId) } } } Fragument ViewModel Repository Database viewModelScopeの生成・破棄(cancel)処理はlifecycle- viewmodel-ktx:2.1.0-alpha01では標準でsupportしています

Slide 58

Slide 58 text

Repository – Suspending functions 58 class GardenPlantingRepository private constructor( private val gardenPlantingDao: GardenPlantingDao ) { suspend fun createGardenPlanting(plantId: String) { withContext(IO) { val gardenPlanting = GardenPlanting(plantId) gardenPlantingDao.insertGardenPlanting(gardenPlanting) } } ... } Fragment ViewModel Repository Database suspend fun createGardenPlanting(plantId: String) { withContext(IO) { val gardenPlanting = GardenPlanting(plantId) gardenPlantingDao.insertGardenPlanting(gardenPlanting) } }

Slide 59

Slide 59 text

Database – Room 59 @Dao interface GardenPlantingDao { @Query("SELECT * FROM garden_plantings") fun getGardenPlantings(): LiveData> ... @Transaction @Query("SELECT * FROM plants") fun getPlantAndGardenPlantings(): LiveData

Slide 60

Slide 60 text

Coroutinesの便利な使い方 60

Slide 61

Slide 61 text

サスペンド関数のテストコード 61 @RunWith(JUnit4::class) class AwaitTest { @Test fun whenAwaitWithResult() { runBlocking { val subject = createSuspendFunction("実行待ち") Truth.assertThat(subject.await()).isEqualTo("実行待ち") } } Blocking Thread

Slide 62

Slide 62 text

ボイラープレートの作成 62 fun loadFlowerList() { viewModelScope.launch { try { loading.value = true flowerRepository.load() } catch (error: FlowerLoadError) { snackbar.value = error.message } finally { loading.value = false } Suspend ロード表示 非表示

Slide 63

Slide 63 text

ボイラープレートの作成 private fun launchBoilerplate(block: suspend () -> Unit): Job { return viewModelScope.launch { try { loading.value = true block() } catch (error: FlowerLoadError) { snackbar.value = error.message } finally { loading.value = false } Suspending Funcation

Slide 64

Slide 64 text

ボイラープレートの作成 64 fun loadFlowerList() { launchBoilerplate { flowerRepository.load() } } Suspend

Slide 65

Slide 65 text

リスナーから値を受け取る 65 suspend fun TargetClass.await(): T { return suspendCoroutine { continuation -> addOnResultListener { result -> when (result) { is Success -> continuation.resume(result.data) is Error -> continuation.resumeWithException(result.error) } } } 継続の作成 suspend fun callTarget() { try { val target = createTagetClass() // リスナーを持つクラス val result = target.await() // 値の返却があるまで中断 } catch (error: TargetException) { // resumeWithException発生時 } } 拡張関数 呼び出し https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/suspend-coroutine.html

Slide 66

Slide 66 text

Coroutinesを使いやすくするライブラリ 66 https://github.com/Kotlin/kotlinx.coroutines/tree/master/reactive/kotlinx-coroutines-rx2 ・kotlinx-coroutines-rx2: RxJavaとの接続性確保。Completable, MayBe, Singleなどの置き換え用のライブラリ ・Android KTX: androidx.lifecycle:lifecycle-viewmodel-ktxでは viewModelScopeを提供 https://developer.android.com/kotlin/ktx

Slide 67

Slide 67 text

他アーキテクチャにおける利 用指標 67

Slide 68

Slide 68 text

問題を振り返る 68 DataSource Repository ViewModel State Operations UI / View Activity Fragment モジュール間でのシーケンスをみてきました

Slide 69

Slide 69 text

問題を捉える(1) 69 DataSource Repository ViewModel State Operations UI / View Activity Fragment データフローの問題としてアーキテクチャを組み立てるのであれば ストリームとして捉えてRxJavaなどを使って解決 一貫性を持ったデータの流れを提供

Slide 70

Slide 70 text

問題を捉える(2) 70 DataSource Repository ViewModel State Operations UI / View Activity Fragment モジュール単位で関心を分離して、独立性の高い開発モデル を主眼とした場合には関心をモジュールという単位で分離する コルーチンを使った関心の分離

Slide 71

Slide 71 text

問題を捉える(3) 71 Repository ViewModel UI / V Activ Fragm 並行性を導入し、責務を単一に分解するための制約を得る DataSource CoroutineScope I/O CoroutineScope Main

Slide 72

Slide 72 text

問題を捉える(4) 72 ViewModel State Operations UI / Activity ライフサイクルに密接に関わる責務の分解 Android specifiedな機能を抽象化し、インターフェイスを提供 Device Camera, GPS, BT Fingerprints

Slide 73

Slide 73 text

まとめ:Androidアプリにおけ るCoroutinesの役割 73

Slide 74

Slide 74 text

まとめ 74 ・コルーチンは並行性を品質よく扱うための仕組み 軽量かつ中断可能、Androidアプリ開発の今後のスタンダード ・コルーチンを知り、ひとりでコードの中を歩けるだけの基礎とアプリへの応用を Kotlin Coroutinesが複雑な非同期処理の課題を解決し、 誰もが理解できるクリーンコードの礎となることを期待しています ・例外の伝搬や、コルーチン間の通信を行うChannel、Selectなどは触れていません Channel等はExperimentalな実装が存在しています。コルーチンの連携を実現する機 能 Session keyword: continuation, concurrency, cancellation, launch, suspend, coroutine scope, async, await, job, coroutine context, withContext, suspendCoroutine

Slide 75

Slide 75 text

まとめ 75 コルーチンを使って アプリ開発をもっと楽しもう! Enjoy Coroutines More fun app development

Slide 76

Slide 76 text

Thank You! [email protected] 76

Slide 77

Slide 77 text

Reference 77 https://github.com/Kotlin/kotlinx.coroutines コルーチンのサイト。いつも関数リファレンスのお世話になってます。 https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html ガイドライン。初めてのひとはここから学べる。Android特有の事情は触れられていない。 https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html メソッドリファレンス https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/ Deferredメソッドリファレンス https://medium.com/@elizarov/blocking-threads-suspending-coroutines-d33e11bf4761 Roman Elizarovさんによる「Blocking threads, suspending coroutines」 https://talks.golang.org/2012/waza.slide#1 Rob Pikeさんの並行性に関するわかりやすい発表「Concurrency is not Parallelism」 https://ja.wikipedia.org/wiki/%E4%B8%A6%E8%A1%8C%E6%80%A7 Wikipedia 並行性 https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3 Wikipedia コルーチン。初出はコンウェイの1963年の論文 https://medium.com/@elizarov/structured-concurrency-722d765aa952 Roman Elizarovさん「Structured concurrency」構造化コンカレンシーについての解説 https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ GoでのStructured concurrencyの解説 http://sys1yagi.hatenablog.com/entry/2018/12/12/224116 yagiさんのブログ https://ja.wikipedia.org/wiki/%E9%96%A2%E5%BF%83%E3%81%AE%E5%88%86%E9%9B%A2 関心の分離 https://xn--97- 273ae6a4irb6e2hsoiozc2g4b8082p.com/%E3%82%A8%E3%83%83%E3%82%BB%E3%82%A4/%E5%8D%98%E4%B8%80%E8%B2%AC%E4%BB%BB%E5%8E%9F %E5%89%87/単一責任原則 https://codelabs.developers.google.com/codelabs/kotlin-coroutines/ Androidアプリ。コルーチンのコードラボ。おすすめ https://kotlinlang.org/docs/reference/coroutines/basics.html 公式リファレンスの基礎編 https://qiita.com/takahirom/items/22a6c6ab4c879dd472e4 takahiromさんによるRxとCoroutinesのバインドのはなし https://resources.jetbrains.com/storage/products/kotlinconf2018/slides/3_Android%20Suspenders.pdf KotlinConf 2018 発表資料 Android Suspenders https://www.youtube.com/watch?v=P7ov_r1JZ1g KotlinConf 2018 - Android Suspenders by Chris Banes https://www.youtube.com/watch?v=a3agLJQ6vt8 KotlinConf 2018 - Kotlin Coroutines in Practice by Roman Elizarov