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

D0a4d1da4644054751e3fa7fd023ad8d?s=47 mhidaka
February 08, 2019

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

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

-----
発表時の内容に誤りがあったのでTypoおよび本資料のP17を修正しています。詳細は次のブログポスト( https://mhidaka.hatenablog.com/entry/2019/02/12/021016 )をご確認ください
-----

Kotlin Coroutinesは非同期処理をシンプルに記述できるKotlinの言語機能です。実験的な機能としてこれまでも提供されてきましたがKotlin 1.3で正式にリリース予定です。

Androidの誕生から10年たちアプリの利用シーンが増えた結果、アプリ開発の複雑さも増してきています。開発者はアプリの性質に合わせてMVVMをはじめとしたアーキテクチャとArchitecture Components(AAC)など複雑性を解消するライブラリを組み合わせ、実装上の課題を解決してきました。

本セッションでは新登場のCoroutinesを既存のアプリケーションへ適用する方法と導入するメリットを中心に解説します。

Coroutinesの本質を理解することはアーキテクチャをよりシンプルに保ちます。そして言語機能の追加がアプリ開発現場に与える影響を考察していきます。

設計・開発の効率化といえば、これまで多くの場合、ライブラリの導入がメインでした。しかしアプリの複雑性が増加するにつれて多数のライブラリを導入しつづけることは開発者に多くの知識を要求し、イニシャルコストの増加に繋がります。言語そのものの力を理解することでコストを下げ、より素早い開発を実現できます。

本セッションはアプリ開発で実践できるよう、なじみのあるアーキテクチャを通じてCoroutinesを学んでいきます。
聞き終わったときには、AACなどモダンなライブラリのなかで効率的な非同期処理に対応する方法を習得できます。

D0a4d1da4644054751e3fa7fd023ad8d?s=128

mhidaka

February 08, 2019
Tweet

Transcript

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

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

    P52 : Fragment (typo) P73:Session Keyword (typo)
  3. @mhidaka 3 TechBooster 技術書典

  4. PEAKS 4

  5. メインポイント / Main points 5

  6. 今日、伝えたいこと 6 ・How to use Kotlin Coroutines ・Apply Coroutines to

    Android App ・When should I use Coroutines
  7. はじめに: コルーチン登場の背景 The first step is always the hardest. 7

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

    of managing background threads
  9. はじめてのコルーチン 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
  10. はじめてのコルーチンと出力 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!
  11. コルーチンの特徴 11 Suspending Coroutines, Blocking Threads CC BY SA 2.0

    https://www.flickr.com/photos/librariesrock/44436145281/
  12. コルーチンの特徴:中断可能 12 ひとくくりの処理に対して中断と再開を提供 Coroutine Task Job A Job B Job

    C Entry Return Job A~Cに対して処理の中断/再開を提供する (スレッドをブロックしない!)
  13. サスペンド関数 - 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
  14. サスペンド関数 - Suspending Functions 14 ・コルーチンはサスペンド関数のタイミングでのみ中断 ・書いた処理を順次に評価して実行する点は変わらない ・launchブロックが中断可能な関数と通常のコードをつなぐ suspend fun

    delay(timeMillis: Long): Unit (source)
  15. コルーチンの特徴(イメージ) 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
  16. コルーチンの特徴(イメージ) 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 関数の実行を中断できる
  17. 継続 - Continuation 17 前の状態を引き継ぐこと。 コンピュータサイエンスではプログラムの実行途 中に、まだ評価していない残りのプログラムや処 理を完了するための手続きや仕組みを指す。

  18. memo 18 スレッドも継続の実装形態のひとつ。 サブルーチンにはコルーチンのような中 断はないため、必ず終了しなければ次の 関数は呼び出せません。マルチスレッド プログラミングによって協調動作し、並 行性を確保している。 スレッドの停止/同期処理はプログラマ の責務。

    2019/2/11 スライドに誤った理解に基づく誤記がありましたので訂正しています。 継続の理解には次の資料がおすすめです http://practical-scheme.net/docs/cont-j.html
  19. コールバックヘルの解決(擬似コード) 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 }
  20. 20 Cancellation CC BY-SA 2.0 https://www.flickr.com/photos/mattx27/5233966181/

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

  22. コルーチンスコープ - 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
  23. コルーチンスコープ - 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
  24. memo 24 アプリケーション開発においては 通常、GrobalScopeは利用しませ ん。アプリ内のライフサイクルに 基づいてCoroutineScopeを独自定 義することを強く推奨しています。

  25. キャンセル処理の例 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.")
  26. キャンセル処理の例 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.") 実行単位・スコープ コルーチンの作成 バックグラウンド・ジョブ
  27. キャンセル処理の例 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.")
  28. 28 Concurrency (and Parallels) CC BY-SA 2.0 https://www.flickr.com/photos/bobellaphotography/8494268626/

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

    並列性: 同時に複数の処理を実行(Execution)すること
  30. 並行性と並列性 – Concurrency and Parallels 30 並行性 並列性 CPU1 CPU2

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

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

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

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

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

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

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

    A CorutineScope B Coroutine … Coroutine …
  38. コルーチンは順次処理が基本 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 { … } // ネットワーク処理
  39. 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
  40. 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 <T> CoroutineScope.async( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): Deferred<T> (source)
  41. 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<out T> : Job (source) abstract suspend fun await(): T
  42. 42 コルーチンは標準で上から下への順 次処理です。並行処理したい場合に async/awaitで待ち合わせます。これ は並行処理の概念が難しく、joinを書 き損じたときなどには複雑な不具合 が発生するための処置です。 余談:C#は並行処理がデフォルトの ため”Classic way“と呼んだことも。

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

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

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

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

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

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

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

  50. 典型的なアプリ構造の課題設定 50 DataSource Repository ViewModel State Operations UI / View

    Activity Fragment モジュール間でのシーケンスを考える
  51. アプリケーションへの適用 51

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

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

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

  55. UI 55 https://github.com/googlesamples/android-sunflower class PlantDetailFragment : Fragment() { override fun

    onCreateView(...): View? { ... val binding = DataBindingUtil.inflate<FragmentPlantDetailBinding>( 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() }
  56. ViewModel – Launch Coroutine 56 class PlantDetailViewModel(...) : ViewModel() {

    ... fun addPlantToGarden() { viewModelScope.launch { gardenPlantingRepository.createGardenPlanting(plantId) } } } Fragment ViewModel Repository Database
  57. 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しています
  58. 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) } }
  59. Database – Room 59 @Dao interface GardenPlantingDao { @Query("SELECT *

    FROM garden_plantings") fun getGardenPlantings(): LiveData<List<GardenPlanting>> ... @Transaction @Query("SELECT * FROM plants") fun getPlantAndGardenPlantings(): LiveData<List<PlantAndGardenPlantings @Insert fun insertGardenPlanting(gardenPlanting: GardenPlanting): Long @Delete fun deleteGardenPlanting(gardenPlanting: GardenPlanting) } Fragment ViewModel Repository Database
  60. Coroutinesの便利な使い方 60

  61. サスペンド関数のテストコード 61 @RunWith(JUnit4::class) class AwaitTest { @Test fun whenAwaitWithResult() {

    runBlocking { val subject = createSuspendFunction("実行待ち") Truth.assertThat(subject.await()).isEqualTo("実行待ち") } } Blocking Thread
  62. ボイラープレートの作成 62 fun loadFlowerList() { viewModelScope.launch { try { loading.value

    = true flowerRepository.load() } catch (error: FlowerLoadError) { snackbar.value = error.message } finally { loading.value = false } Suspend ロード表示 非表示
  63. ボイラープレートの作成 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
  64. ボイラープレートの作成 64 fun loadFlowerList() { launchBoilerplate { flowerRepository.load() } }

    Suspend
  65. リスナーから値を受け取る 65 suspend fun <T> TargetClass<T>.await(): T { return suspendCoroutine

    { continuation -> addOnResultListener { result -> when (result) { is Success<T> -> 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
  66. 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
  67. 他アーキテクチャにおける利 用指標 67

  68. 問題を振り返る 68 DataSource Repository ViewModel State Operations UI / View

    Activity Fragment モジュール間でのシーケンスをみてきました
  69. 問題を捉える(1) 69 DataSource Repository ViewModel State Operations UI / View

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

    Activity Fragment モジュール単位で関心を分離して、独立性の高い開発モデル を主眼とした場合には関心をモジュールという単位で分離する コルーチンを使った関心の分離
  71. 問題を捉える(3) 71 Repository ViewModel UI / V Activ Fragm 並行性を導入し、責務を単一に分解するための制約を得る

    DataSource CoroutineScope I/O CoroutineScope Main
  72. 問題を捉える(4) 72 ViewModel State Operations UI / Activity ライフサイクルに密接に関わる責務の分解 Android

    specifiedな機能を抽象化し、インターフェイスを提供 Device Camera, GPS, BT Fingerprints
  73. まとめ:Androidアプリにおけ るCoroutinesの役割 73

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

    能 Session keyword: continuation, concurrency, cancellation, launch, suspend, coroutine scope, async, await, job, coroutine context, withContext, suspendCoroutine
  75. まとめ 75 コルーチンを使って アプリ開発をもっと楽しもう! Enjoy Coroutines More fun app development

  76. Thank You! rabbitlog@gmail.com 76

  77. 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