【问题标题】:Catch exception in Kotlin async coroutines and stop propagating在 Kotlin 异步协程中捕获异常并停止传播
【发布时间】:2020-04-19 19:03:45
【问题描述】:

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

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

failedConcurrentSum 中的try-catch 不处理val two 抛出的异常。 我可以说服自己这是由于“结构化并发”。

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

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

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

【问题讨论】:

    标签: asynchronous kotlin kotlin-coroutines


    【解决方案1】:

    coroutineScope 只是一个函数,它在内部设置它的作用域,从外部它总是像一个常规函数一样完成,不会与任何外部作用域混淆。这是因为它不会泄漏在其范围内启动的任何并发协程。您始终可以可靠地捕获和处理coroutineScope 引发的异常。

    另一方面,async 在启动协程后立即完成,因此您有两个并发的协程:一个运行async 代码,另一个调用相应的await。由于async 也是调用await 的那个的孩子,它的失败会在父的await 调用完成之前取消父。

    failedConcurrentSum 中的 try-catch 不处理 val two 抛出的异常。

    如果有机会,它确实会这样做。但是由于 try-catch 块位于与完成val twoDeferred 的协同程序同时运行的协同程序中,因此在由于子协同程序失败而被取消之前,它没有机会这样做。

    【讨论】:

      【解决方案2】:

      coroutineScope 使用 Job

      默认情况下,作业的任何子级失败会导致其父级立即失败并取消其其余子级。 Job

      您可以使用supervisorScope 代替coroutineScope

      子项的失败或取消不会导致主管作业失败,也不会影响其其他子项。 SupervisorJob

      但您必须等待第一个 async 块完成。

      try catch 中使用coroutineScope 可以在发生异常时立即返回默认值

      suspend fun failedConcurrentSum() = try {
          coroutineScope {
              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
      }
      

      【讨论】:

      • 所以在我的第二个示例中,我可以捕获异常,因为内部异步块取消了包装 coroutineScope,但没有取消包装整个 failedConcurrentSum,对吗?
      • @JayLee 是的,在第二个示例中,async 块仅导致其父 coroutineScope 失败,并且不影响外部 coroutineScope
      猜你喜欢
      • 2016-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-01
      • 1970-01-01
      • 2019-01-15
      • 1970-01-01
      • 2020-02-01
      相关资源
      最近更新 更多