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