Coroutines私的メモ2~Dispatchers, withContext, async/await~

[私的メモ1](https://qiita.com/sudo5in5k/items/90b92eb5b7f43ce4ecf5)の続きです

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

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

# dispatchers
コルーチンを使う際に、どのスレッドもしくはスレッドプールにて実行・制御したいということがあります

その際にdispatcherを指定することで解決できます Dispatcherにもいくつか種類があるので使い分けたいところです

## Dispatchers.Main
UIに関する処理をするメインスレッドのためのコルーチン

## Dispatchers.Main.immediate
UI更新を即時行いたい際に使用される (e.g. textviewのtext更新とか)

## Dispatchers.Default
共有スレッドプールを使用し、CPUのコア数に従って設定される
CPUに負荷をかけるような計算をするような処理に対して使うことが推奨されます

## Dispatchers.IO
その処理に必要なコルーチンの数に応じて必要な分だけ共有スレッドプールを使用する
DBの検索等、I/Oに負荷をかけるような処理に対して使うことが推奨されます

## Dispatchers.Unconfined
特定のスレッドに限定させない
一度停止させて再開した場合は別スレッドに切り替わっている可能性あり

試しにこのようなプログラムを組んでみます

```kotlin
fun unconfined() = runBlocking {
    launch(Dispatchers.Unconfined) {
        logger("Hello")
        delay(1000L)
        logger("World")
    }
}
```

実行結果はこのように、スレッドが異なっていますね

```
main : Hello
kotlinx.coroutines.DefaultExecutor : World
```

# withContext
コルーチンの実行スレッドを切り替えたいときに使う
withContextの引数に上述したDispatcherを指定してあげる
そのため、切り替える必要がない単純な場合は`launch(対象のDispatcher){}`で事足りると思います

例としては下記のように、ローディングを走らせ、別のスレッドでデータを引っ張ってきて、そのデータを元にUIを更新するなどです

`loading()` と `updateList(data)` はtop-levelで指定しているMainスレッドで処理されますが、データをひっぱってきている `fetchData`は別スレッドでの処理になります

```kotlin
fun update() {
  launch(Dispatchers.Main) {
    loading()
    val data = withContext(Dispatchers.Default) { repository.fetchData() }
    updateList(data)
  }
}
```

# async/await
コルーチンからの返り値を使って複数の結果を待ち合わせて処理したい場合に用いられます

例えば上述した updateメソッドのfetchDataが他のデータも必要な場合ですね

```kotlin
fun update() {
  launch(Dispatchers.Main) {
    loading()
    val feedData = async (Dispatchers.Default) { repository.fetchFeed() }
    val adData = ahync (Dispatchers.Default) { repository.fetchAd() }
    val data = feedData.await() + adData.await()
    updateList(data)
  }
}
```

`withTimeout` or `withTimeoutOrNull`でタイムアウトの指定もできます
ネットワーク環境が悪かったり、遅い結果でユーザ体験を損なわせないことを目的として使えそうですね

単純な例を示します

```kotlin
fun asyncAwaitWithTimeout() {
    runBlocking {
        logger("Hello")
        val simpleTask = async(Dispatchers.Default) {
            2 + 2
        }

        val heavyTask = async(Dispatchers.Default) {
            logger("World")
            delay(3000L)
            4 + 4
        }

        logger("Calculating...")
        val total = withTimeoutOrNull(2) { simpleTask.await() + heavyTask.await() }
        logger("total=$total")
    }
}
```

ただ足し算をしてその結果を返しているだけですね
ここでは、簡単なタイムアウトを2秒と定義し、時間のかからない簡単なタスクと実行に最低3秒はかかってしまう重いタスクを定義して、わざとタイムアウトさせるようにしています

結果としては以下の様になります

```
main : Hello
main : Calculating...
DefaultDispatcher-worker-1 : World
main : total=null
```

今回は`withTimeoutOrNull`を使ったので、合計の12が返るのではなく、タイムアウトしたのでnullが返りました

次回はCoroutines Flowを扱う予定です

# 参考

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

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

* [withContext](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html)

* [MainCoroutineDispatcher](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-coroutine-dispatcher/index.html)