
【Kotlin】【Coroutines】asyncの挙動を確認する
2023.06.12
はじめに
Android Coroutines Async の挙動を確認したのでメモとして残しておく
1.準備
環境情報
1 2 3 4 5 6 7 8 | // Coroutines dependencies { <省略> def coroutines_android_version = '1.6.0' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_android_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_android_version" <省略> } |
suspend関数作成
まずはasyncで動作させるsuspend関数を作成
1 2 3 4 5 6 7 8 9 10 11 12 | suspend fun mySuspend(name: String, delayTimeMillis: Long): Int { return withContext(Dispatchers.Default) { log.emit("---------start mySuspend() $name---------") var result = 0 (1..5).forEach { result += (1..100).random() delay(delayTimeMillis) log.emit("mySuspend() $name - $it") } log.emit("---------finish mySuspend() $name---------") return@withContext result } |
例外発生用のsuspend関数作成
例外を発生させる関数も作っておく
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Suppress("UNREACHABLE_CODE") suspend fun error(name: String, crashTime: Int = 5): Int { withContext(Dispatchers.Default) { log.emit("---------start error() $name---------") (crashTime downTo 0).forEach { if (it <= 0) { log.emit("throw Exception!") throw Exception("error") } log.emit("throw Exception まで あと${it}秒") delay(1000L) } log.emit("---------finish error() $name---------") } return (1..100).random() } |
ログ表示用のLiveData用意しておく
TextViewにログを表示するために実装(必須ではない)
1 2 3 4 5 6 7 8 9 10 11 | val log = MutableSharedFlow<String>(replay = 100) val logTextLiveData: LiveData<String> = log.mapLatest { Log.d("my_log", it) val stringBuilder = StringBuilder() log.replayCache.forEach { stringBuilder.append(it) stringBuilder.append("\n") } stringBuilder.toString() }.asLiveData() |
2.まずは基本
asyncを使わず普通にsuspendを呼び出す
2つのsuspend関数を直列に処理する
1 2 3 4 5 6 | launch { val a = mySuspend("A", 1000L) val b = mySuspend("B", 300L) log.emit("a = $a") log.emit("b = $b") } |
ログを確認すると
mySuspend() Aの完了後にmySuspend() Bが実行されている
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ---------start mySuspend() A--------- mySuspend() A - 1 mySuspend() A - 2 mySuspend() A - 3 mySuspend() A - 4 mySuspend() A - 5 ---------finish mySuspend() A--------- ---------start mySuspend() B--------- mySuspend() B - 1 mySuspend() B - 2 mySuspend() B - 3 mySuspend() B - 4 mySuspend() B - 5 ---------finish mySuspend() B--------- a = 233 b = 325 |
普通にAsyncを使ってみる
2つのsuspend関数をそれぞれasyncで囲んで、それぞれawaitで取得する
1 2 3 4 5 6 7 8 | launch { val aDeferred = async { mySuspend("A", 1000L) } val bDeferred = async { mySuspend("B", 300L) } val a = aDeferred.await() val b = bDeferred.await() log.emit("a = $a") log.emit("b = $b") } |
ログを確認すると
mySuspend() AとmySuspend() Bが並列で実行されている
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ---------start mySuspend() A--------- ---------start mySuspend() B--------- mySuspend() B - 1 mySuspend() B - 2 mySuspend() B - 3 mySuspend() A - 1 mySuspend() B - 4 mySuspend() B - 5 ---------finish mySuspend() B--------- mySuspend() A - 2 mySuspend() A - 3 mySuspend() A - 4 mySuspend() A - 5 ---------finish mySuspend() A--------- a = 130 b = 316 |
3. asyncとsuspend関数を組み合わせたときの実行順番
2つのawaitの間で別のsuspendを実行
1 2 3 4 5 6 7 8 9 10 | launch { val aDeferred = async { mySuspend("A", 300L) } val bDeferred = async { mySuspend("B", 1000L) } val a = aDeferred.await() val c = mySuspend("C", 700L) // ←2つのawaitの間で実行 val b = bDeferred.await() log.emit("a = $a") log.emit("b = $b") log.emit("c = $c") } |
ログを確認すると
mySuspend() AとmySuspend() Bが並列で実行され、mySuspend() Aの完了を待ってからmySuspend() Cが実行されている(mySuspend() BとmySuspend() Cは並列で実行される)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | ---------start mySuspend() A--------- ---------start mySuspend() B--------- mySuspend() A - 1 mySuspend() A - 2 mySuspend() A - 3 mySuspend() B - 1 mySuspend() A - 4 mySuspend() A - 5 ---------finish mySuspend() A--------- ---------start mySuspend() C--------- mySuspend() B - 2 mySuspend() C - 1 mySuspend() C - 2 mySuspend() B - 3 mySuspend() C - 3 mySuspend() B - 4 mySuspend() C - 4 mySuspend() B - 5 ---------finish mySuspend() B--------- mySuspend() C - 5 ---------finish mySuspend() C--------- a = 295 b = 330 c = 284 |
2つのasyncの間で別のsuspendを実行
1 2 3 4 5 6 7 8 9 10 | launch { val aDeferred = async { mySuspend("A", 1000L) } val b = mySuspend("B", 700L) // ←2つのasyncの間で実行 val cDeferred = async { mySuspend("C", 300L) } val a = aDeferred.await() val c = cDeferred.await() log.emit("a = $a") log.emit("b = $b") log.emit("c = $c") } |
ログを確認すると
mySuspend() AとmySuspend() Bが並列で実行され、mySuspend() Bの完了を待ってからmySuspend() Cが実行されている(mySuspend() AとmySuspend() Cは並列で実行される)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | ---------start mySuspend() B--------- ---------start mySuspend() A--------- mySuspend() B - 1 mySuspend() A - 1 mySuspend() B - 2 mySuspend() A - 2 mySuspend() B - 3 mySuspend() B - 4 mySuspend() A - 3 mySuspend() B - 5 ---------finish mySuspend() B--------- ---------start mySuspend() C--------- mySuspend() C - 1 mySuspend() A - 4 mySuspend() C - 2 mySuspend() C - 3 mySuspend() C - 4 mySuspend() A - 5 ---------finish mySuspend() A--------- mySuspend() C - 5 ---------finish mySuspend() C--------- a = 341 b = 331 c = 338 |
4. 特殊な実装をした場合
普通やらないやり方で実装したとき、どのような挙動になるか?
2つのasyncを実行し、片方のawaitを呼ばない
1 2 3 4 5 6 7 | launch { val aDeferred = async { mySuspend("A", 1000L) } val bDeferred = async { mySuspend("B", 1500L) } val a = aDeferred.await() log.emit("a = $a") log.emit("b = bDeferred.await() を書かない") } |
ログを確認すると
mySuspend() Bの完了を待たずに、launchのブロックを最後まで実行されている(launchブロック完了後も残りを実行し続ける)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ---------start mySuspend() A--------- ---------start mySuspend() B--------- mySuspend() A - 1 mySuspend() B - 1 mySuspend() A - 2 mySuspend() B - 2 mySuspend() A - 3 mySuspend() A - 4 mySuspend() B - 3 mySuspend() A - 5 ---------finish mySuspend() A--------- a = 306 b = bDeferred.await() を書かない ↑======== launchブロックはここまで ========↑ mySuspend() B - 4 // ←launchのブロックが終了後もBの残りを実行し続ける mySuspend() B - 5 // ←launchのブロックが終了後もBの残りを実行し続ける ---------finish mySuspend() B--------- |
1つ目のasyncをlaunch前に実行して、launch内部でawaitする
1 2 3 4 5 6 7 8 | val aDeferred = async { mySuspend("A", 1000L) } // ←asyncをlaunch前に実行する launch { val bDeferred = async { mySuspend("B", 1500L) } val a = aDeferred.await() val b = bDeferred.await() log.emit("a = $a") log.emit("b = $b") } |
ログを確認すると
mySuspend() Aがlaunch前に開始されている
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ---------start mySuspend() A--------- // ←launch前に開始されている // ↓ここからlaunch内部 ---------start mySuspend() B--------- mySuspend() A - 1 mySuspend() B - 1 mySuspend() A - 2 mySuspend() B - 2 mySuspend() A - 3 mySuspend() A - 4 mySuspend() B - 3 mySuspend() A - 5 ---------finish mySuspend() A--------- mySuspend() B - 4 mySuspend() B - 5 ---------finish mySuspend() B--------- a = 198 b = 280 |
5.例外を絡めた挙動
長くなるので次の記事に記載します。(むしろここからが本番です)
書いた人はこんな人
