async を使ってるのに例外をawaitで捕獲できずアプリが落ちる…? というのを体験した。

理由の説明はこの記事が詳しかった。 https://star-zero.medium.com/coroutines-asyncとexception-9c0f079edb0e

  • 親のキャンセルは子に伝えたい
  • 子の例外はawait()で受け取りたい

…という用途では supervisorScope{} を使うのが良さそう。

スクラッチで試してみて概ね期待どおりだった。

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import java.lang.Thread.sleep

val scope = CoroutineScope(Job())

val job = scope.launch {
    try {
        supervisorScope {
            val tasks = listOf(
                async {
                    try {
                        delay(333L)
                        error("Test")
                    } catch (ex: Throwable) {
                        println("catch ex in async. ${ex.message}")
                        throw ex
                    }
                }
            )
            try {
                tasks.awaitAll()
            } catch (ex: Throwable) {
                println("catch ex in awaitAll. ${ex.message}")
                throw ex
            }
            println("awaitAll complete.")
        }
        println("supervisorScope complete.")
    } catch (ex: Throwable) {
        println("catch ex in launch. ${ex.message}")
        ex.printStackTrace()
    }
    println("launch complete.")
}
sleep(100L)
runBlocking {
    // async内部からの例外発生を試したい場合
    job.join()
    // catch ex in async. Test
    // catch ex in awaitAll. Test
    // catch ex in launch. Test
    // launch complete.

    // または、上流からのキャンセル到達を試したい場合
    // job.cancelAndJoin()
    // catch ex in awaitAll. StandaloneCoroutine was cancelled
    // catch ex in async. StandaloneCoroutine was cancelled
    // catch ex in launch. StandaloneCoroutine was cancelled
    // launch complete.
}
println("end.")