Exception thrown by deferred.await() within a runBlocking treated as unhandled even after caught

前端 未结 4 1022
时光说笑
时光说笑 2021-02-14 10:20

This code:

fun main() {
    runBlocking {
        try {
            val deferred = async { throw Exception() }
            deferred.await()
        } catch (e: E         


        
4条回答
  •  北海茫月
    2021-02-14 10:51

    After studying the reasons why Kotlin introduced this behavior I found that, if the exceptions weren't propagated this way, it would be complicated to write well-behaved code that gets cancelled in a timely fashion. For example:

    runBlocking {
        val deferredA = async {
            Thread.sleep(10_000)
            println("Done after delay")
            1
        }
        val deferredB = async { throw Exception() }
        println(deferredA.await() + deferredB.await())
    }
    

    Because a is the first result we happen to wait for, this code would keep running for 10 seconds and then result in an error and no useful work achieved. In most cases we'd like to cancel everything as soon as one component fails. We could do it like this:

    val (a, b) = awaitAll(deferredA, deferredB)
    println(a + b)
    

    This code is less elegant: we're forced to await on all results at the same place and we lose type safety because awaitAll returns a list of the common supertype of all arguments. If we have some

    suspend fun suspendFun(): Int {
        delay(10_000)
        return 2
    }
    

    and we want to write

    val c = suspendFun()
    val (a, b) = awaitAll(deferredA, deferredB)
    println(a + b + c)
    

    We're deprived of the opportunity to bail out before suspendFun completes. We might work around like this:

    val deferredC = async { suspendFun() }
    val (a, b, c) = awaitAll(deferredA, deferredB, deferredC)
    println(a + b + c)
    

    but this is brittle because you must watch out to make sure you do this for each and every suspendable call. It is also against the Kotlin doctrine of "sequential by default"

    In conclusion: the current design, while counterintuitive at first, does make sense as a practical solution. It additionally strengthens the rule not to use async-await unless you're doing parallel decomposition of a task.

提交回复
热议问题