首页 > 解决方案 > 在 Kotlin 异步协程中捕获异常并停止传播

问题描述

我想捕获从异步协程抛出的异常。下面的代码演示了一个问题:

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    try {
        println(failedConcurrentSum())
    } catch (e: ArithmeticException) {
        println("Computation failed with ArithmeticException")
    }
}

suspend fun failedConcurrentSum() = coroutineScope {
    try {
        val one = async {
            try {
                delay(1000L)
                42
            } finally {
                println("First child was cancelled")
            }
        }

        val two = async<Int> {
            println("Second child throws an exception")
            throw ArithmeticException()
        }

        one.await() + two.await()
    } catch (e: ArithmeticException) {
        println("Using a default value...")
        0
    }
}

这打印:

Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException

内部try-catchfailedConcurrentSum处理val two. 我可以说服自己,这是由于“结构化并发”。

但是,这并不能解释为什么将async' 包装在 acoroutineScope中会捕获异常:

suspend fun failedConcurrentSum() = coroutineScope {
    try {
        val one = coroutineScope {
            async {
                try {
                    delay(1000L)
                    42
                } finally {
                    println("First child was cancelled")
                }
            }
        }

        val two = coroutineScope {
            async<Int> {
                println("Second child throws an exception")
                throw ArithmeticException()
            }
        }

        one.await() + two.await()
    } catch (e: ArithmeticException) {
        println("Using a default value...")
        0
    }
}

这打印:

First child was cancelled
Second child throws an exception
Using a default value...
0

为什么后者捕获异常而第一个没有?

标签: asynchronouskotlinkotlin-coroutines

解决方案


coroutineScope is just a function, it sets up its scope internally and from the outside it always completes like a regular function, not messing with any outer scopes. This is because it doesn't leak any concurrent coroutines started within its scope. You can always reliably catch and handle exceptions thrown by coroutineScope.

async, on the other hand, completes immediately after launching the coroutine, therefore you have two concurrent coroutines: one running the async code and another calling the corresponding await. Since the async one is also the child of the one calling await, its failure cancels the parent before the parent's await call completes.

The try-catch inside the failedConcurrentSum doesn't handle the exception thrown by val two.

It actually does, if it gets the chance. But since the try-catch block is in a coroutine that runs concurrently to the one completing val two's Deferred, it just doesn't get the chance to do so before being cancelled due to the failure of the child coroutine.


推荐阅读