【Kotlin研修9日目】コルーチンを利用した非同期処理の実装

# コルーチンを利用した非同期処理
[参考: AndroidでのKotlinコルーチン](https://developer.android.com/kotlin/coroutines)
[参考: 図で理解するKotlinコルーチン](https://qiita.com/kawmra/items/d024f9ab32ffe0604d39)

## コルーチン
`非同期処理`が含まれる`ブロック{...}`(=`コルーチンスコープ`)内で、`非同期処理`の実行中に、`同期処理`を中断できる仕組み(=`デザインパターン`)。

`UIスレッド`の処理によって呼び出された、`ワーカースレッド`での処理を含む`非同期処理`のまとまり。

> `コルーチン`を起動する、`UIスレッド`で動作する処理を`処理A`、
`処理A`によって呼び出された`ワーカースレッド`で動作する処理を`処理B`、
`処理B`によって呼び出された`UIスレッド`で動作する処理を`処理C`とすると、
`処理C`は`処理B`の終了を待つ必要があるため、同一`処理ブロック{}`でまとめて管理するのが望ましく、この処理のまとまりを`コルーチン`と呼ぶ。<br>
また、`処理B`と`処理C`は互いに異なる`スレッド`上で動作するため、双方から見て`非同期処理`と言える。

### コルーチンスコープ
`コルーチン`の**生存可能環境**。
`コルーチン`を管理しており、`コルーチン`の起動や実行中止を行う。

`ViewModel`でコルーチンを定義した場合は`ViewModel`クラスの`viewModelScope`プロパティ、
`アクティビティ`でコルーチンを定義した場合は`Activity`クラスの`lifecycleScope`プロパティが`コルーチンスコープ`にあたる。

***

# コルーチンを利用した非同期処理の実装
`コルーチン`を利用した`非同期処理`を実装する手順は、以下の通り。

> 1. `Gradle Scripts/build.gradle(Module)`の`dependencies`ブロックに、`コルーチン`の実装に必要な追加ライブラリを記述
> 2. `コルーチン`の定義
> 3. `コルーチンスコープ`から2.で定義した`コルーチン`を起動

## build.gradle(Module)への追加ライブラリの記述
`コルーチン`の実装に必要な機能は標準`SDK`で定義されていないため、追加ライブラリを`Gradle Scripts`フォルダの`build.gradle(Module)`ファイルに記述する必要がある。

また、`ライブラリ`のバージョンは以下サイトを参考に、安定版の最新バージョン番号に書き換える。
[Kotlinコルーチン](https://github.com/Kotlin/kotlinx.coroutines/releases)
[AndroidX Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle)

### サンプルコード

```gradle:build.gradle(Module)
...
dependencies {
    // Kotlinコルーチン
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0"
    // AndroidX Lifecycle
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
    ...
}
```

## コルーチンの定義
[参考: CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/)
`非同期処理`を行う処理を記述する際、
`ワーカースレッド`での動作を保証する`@WorkerThread`アノテーションの記述に加え、
`呼び出し元スレッド`での処理を中断させながら、指定`スレッド`で処理を実行できる`withContext()`メソッドを利用する。

なお、`withContext()`メソッドは内部的に`suspend`キーワードが付与されているため、`withContext()`メソッドを利用する親メソッドにも`suspend`キーワードを付与する必要がある。

### 定義

```kotlin
// スレッドを分離して処理を実行
// <- 内部的にsuspendキーワードが付与
suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T
// パラメータ
// context: 分離先スレッド(Dispatchersクラス定数)
// block: スレッドを分離して実行する処理
```

#### Dispatchersクラス定数

|クラス定数|内容|
|:--|:--|
|`Dispatchers.Main`|`UIスレッド`<br>用途:<br>`UI`操作|
|`Dispatchers.IO`|`ワーカースレッド`(`I/Oスレッド`)<br>用途:<br>`Room`コンポーネント<br>ファイルの`読み書き`<br>`ネットワークオペレーション`|
|`Dispatchers.Default`|`ワーカースレッド`(`Defaultスレッド`)<br>用途:<br>`CPU負荷`の高い作業|

### サンプルコード

```kotlin:MainActivity.kt
// 非同期(=ワーカースレッド)で実行する処理
// -> ワーカースレッドで動作することを明示的に記述(@WorkerThreadアノテーション)
// suspend:　実行中は元スレッド(=UIスレッド)の他の処理を中断させる
@WorkerThread
private suspend fun backgroundTaskRunner(): String {
    // ワーカースレッドで取得した値
    val returnVal = withContext(Dispatchers.IO) {
        ...  // ワーカースレッドで値を取得
    }
    // 取得した値を返却
    return returnVal
}
```

#### suspendシグネチャ
`suspend`シグネチャが付与された`メソッド`の実行中に、メソッドの`呼び出し元スレッド`での処理を**中断**させるキーワード。

## コルーチンスコープによるコルーチンの起動
`アクティビティ`で`コルーチン`を定義した場合、`アクティビティ`がもつ`lifecycleScope`プロパティを利用して`コルーチン`を起動する。

`ViewModel`でコルーチンを定義した場合、`ViewModel`クラスの`viewModelScope`プロパティを利用して`コルーチン`を起動する。

### 定義

```kotlin
CoroutineScope.launch(
    context: CoroutineContext
    start: CoroutineStart
    block: suspend CoroutineScope.() -> Unit
)
// パラメータ
// context: コルーチンスコープへの追加コンテキスト
// -> 未指定の場合はDispatchers.Default(コルーチンスコープから継承)
// start: コルーチンの開始タイミング(CoroutineStartクラス定数)
// -> 未指定の場合はCoroutineStart.Default(即時実行)
// block: 起動するコルーチン
```

### サンプルコード

```kotlin:MainActivity.kt
@UiThread
private fun asyncExecute() {

    // アクティビティのコルーチンスコープによるコルーチンの起動
    lifecycleScope.launch {

        // コルーチン
        // ワーカースレッドで動作する非同期処理
        backgroundTaskRunner()

        // UIスレッドで動作する非同期処理
        postBackgroundTaskRunner()
    }
}
```

## コルーチンを利用した非同期処理の実装まとめ

```kotlin
// UIスレッドで動作する非同期処理を含む処理
@UiThread
private fun asyncExecute() {
    //　ライフサイクルに最適化されたスコープを利用してコルーチンを起動
    lifecycleScope.launch {
        // 非同期(=ワーカースレッド)で行う処理
        backgroundTaskRunner()
        // 非同期処理後に同期(=UIスレッド)で行う処理
        postBackgroundTaskRunner()
    }
}

// ワーカースレッドで動作する処理(非同期処理)
@WorkerThread
private suspend fun backgroundTaskRunner() {
    withContext(Dispatchers.IO) {
        ...  // 非同期処理
    }
}

// 非同期処理後にUIスレッドで動作する処理(同期処理)
@UiThread
private fun postBackgroundTaskRunner() {
    ...  // 同期処理
}
```