• トップ
  • ブログ一覧
  • 【Kotlin】【Coroutines】asyncの挙動を確認する
  • 【Kotlin】【Coroutines】asyncの挙動を確認する

    はじめに

    Android Coroutines Async の挙動を確認したのでメモとして残しておく

    1. 準備
    2. まずは基本
    3. asyncとsuspend関数を組み合わせたときの実行順番
    4. 特殊な実装をした場合
    5. 例外を絡めた挙動

    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() AmySuspend() 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() AmySuspend() 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() AmySuspend() B並列で実行され、mySuspend() B完了を待ってからmySuspend() Cが実行されている(mySuspend() AmySuspend() 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() Alaunch前に開始されている

    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.例外を絡めた挙動

    長くなるので次の記事に記載します。(むしろここからが本番です)

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background