LoginSignup
9
6

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-02

私的メモ1の続きです

前提

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

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

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

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

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は別スレッドでの処理になります

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

async/await

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

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

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

単純な例を示します

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を扱う予定です

参考

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6