Coroutines私的メモ1~launch, runBlocking, scope, exception handling~

これは私的なCoroutinesに関するメモです。
今回はlaunch, runBlocking, join, supervisorScopeについて触れます。
もし指摘点ありましたら遠慮なくまさかり飛ばしてもらえればと思います。

# Coroutinesとは

中断(suspend)と再開(resume)の機能によってスレッドよりも**低コスト・可読性高く非同期処理を扱える**

# 導入

Kotlin1.3でstableになりました。

```gradle
dependencies {
  def coroutines_version = "1.3.2"
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
}
```

stableは[こちら](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core)で確認できます

# 前提
現在のスレッドがわかるようにloggerメソッドを仕込むことにします

```kotlin
fun logger(msg: String) = println("${Thread.currentThread().name} : $msg")
```

# GlobalScope.launch

`GlobalScope`はコルーチンの**スコープ**で、すべてのコルーチンはいずれかのスコープに紐づくことになっています。

```kotlin
fun normalLaunch() {
    logger("start")
    GlobalScope.launch {
        logger("Hello")
    }
    logger("World")
}
```

試しにこのようなコードを書いた場合は

```
main : start
main : World
```

になります。`launch`はその実行中のスレッド(今回は`main`)をブロックしないためです。

そのため処理したいならほんの少しだけスレッドを停止させてあげれば可能です。

```kotlin
fun normalLaunch() {
    logger("start")
    GlobalScope.launch {
        logger("Hello")
    }
    Thread.sleep(100L)
    logger("World")
}
```

実行結果はこのように

```
main : start
DefaultDispatcher-worker-1 : Hello
main : World
```

ただしアプリ開発やサーバサイド開発するうえで、このようなコードをずっと書いていくわけにもいかないと思います。

# runBlocking

そこで、`runBlocking`を使います。このブロックで囲まれた部分は現在のスレッドをブロックして実行します。

```kotlin
fun normalRunBlocking() {
    logger("start")
    runBlocking {
        logger("Hello")
    }
    logger("World")
}
```

結果は

```
main : start
main : Hello
main : World
```

runBlockingのブロックが終了することを待ってから最後のloggerへ移ります。

では、コルーチン内で他のコルーチンを記述する場合はどうなるのでしょうか。(=親子関係)

# Job#Join

子の`GlobalScope.launch`で記述された`Job`に対して`join()`することで親のコルーチンは子のコルーチンの終了を待機します。
変数宣言せずそのまま記述してもよいのですがこちらのほうが管理がずっと楽になりますね。

```
fun parentChild() {
    val job = GlobalScope.launch {
        delay(1000L)
        logger("Hello")
    }
    logger("start")
    runBlocking {
        job.join()
        logger("World")
    }
    logger("end")
}
```

結果としては

```
main : start
DefaultDispatcher-worker-1 : Hello
main : World
main : end
```

# Child coroutinesの扱い

このようなコードを仕込んでみます
2つの子関係にあるコルーチンが存在しています

```kotlin
fun parentChildWhenException() {
    suspend fun child1() {
        delay(2000L)
        logger("child1")
    }
    suspend fun child2() {
        logger("child2")
        delay(1000L)
        throw RuntimeException("Exception!")
    }
    try {
        logger("start")
        runBlocking {
            launch { child1() }
            launch { child2() }
        }
        logger("end")
    } catch (e: Exception) {
        logger("${e.message}")
    } finally {
        logger("finally")
    }
}
```

実行すると以下になります

```
main : start
main : child2
main : Exception!
main : finally
```

子関係にある`child2()`側で1秒後に例外が投げられることで、child1()は実行されず親のコルーチンがキャンセルされます。
ではchildに対してExceptionをそれぞれ管理しつつきちんと実行されるにはどうしたらよいでしょう。

# supervisorScope

childに関係なく実行させたい場合は`supervisorScope`を使います。試しに上記のコードにそれだけを挿入したコードを実行してみます。

```kotlin
fun supervisorScope() {
    suspend fun child1() {
        delay(2000L)
        logger("child1")
    }

    suspend fun child2() {
        logger("child2")
        delay(1000L)
        throw RuntimeException("Exception!")
    }
    try {
        logger("start")
        runBlocking {
            supervisorScope {
                launch { child1() }
                launch { child2() }
            }
        }
        logger("end")
    } catch (e: Exception) {
        logger("${e.message}")
    } finally {
        logger("finally")
    }
}
```

結果としては

```
main : start
main : child2
Exception in thread "main" java.lang.RuntimeException: Exception!
	at com.example.todoappsandbox.CoroutinesKt$supervisorScope$2.invokeSuspend(Coroutines.kt:76)
	at .. (略)
main : child1
main : end
main : finally
```

のようになり、child1自体も実行されるようになりました。一方catchされずExceptionが挙げられてしまいました。supervisorを使う場合は子の例外が親に伝播されないためです。そのため、子自身に例外処理を定義する必要があります。

## CoroutineExceptionHandler

`CoroutineExceptionHandler`を使って例外を定義できます。
それを対象の子のコルーチンに挿入して対応ができます。(`launch(handler)`)

```kotlin
fun supervisorScopeHandleChildException() {
    suspend fun child1() {
        delay(2000L)
        logger("child1")
    }

    suspend fun child2() {
        logger("child2")
        delay(1000L)
        throw RuntimeException("Exception!")
    }

    val handler = CoroutineExceptionHandler {_, e ->
        logger("Handle Child Exception: ${e.message}")
    }
    try {
        logger("start")
        runBlocking {
            supervisorScope {
                launch { child1() }
                launch(handler) { child2() }
            }
        }
        logger("end")
    } catch (e: Exception) {
        logger("${e.message}")
    } finally {
        logger("finally")
    }
}
```

これで例外なく全ての子関係のコルーチンが実行できるようになりました。

```
main : start
main : child2
main : Handle Child Exception: Exception!
main : child1
main : end
main : finally
```


次回はdispatchers, withContext, async/awaitあたりを触れていきたいです

# 参考

* [ていねいに学ぶAndroidアプリ開発 Android 10 Modern Functions 【技術書典7新刊】](https://booth.pm/ja/items/1509191)

* [coroutine-guide](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html)