私的メモ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を扱う予定です