【问题标题】:When you throw an exception in a coroutine scope, is the coroutine scope reusable?当你在协程范围内抛出异常时,协程范围是否可重用?
【发布时间】:2019-05-02 02:41:24
【问题描述】:

我在使用协程进行错误处理时遇到问题,我已通过以下步骤将其范围缩小到此单元测试:

  1. 我使用任何调度程序创建一个协程范围。
  2. 我在异步块(甚至嵌套的异步块)中此范围内的任何位置抛出异常。
  3. 我对返回的延迟值调用 await 并处理异常。
  4. 这一切都很好。但是,当我尝试使用相同的协程范围来启动新的协程时,这总是异常完成并出现相同的异常。

    这是测试:

    fun `when you throw an exception in a coroutine scope, is the coroutine scope dead?`() {
        val parentJob = Job()
        val coroutineScope = CoroutineScope(parentJob + Dispatchers.Default)
    
        val deferredResult = coroutineScope.async { throw IllegalStateException() }
    
        runBlocking {
            try {
                deferredResult.await()
            } catch (e: IllegalStateException) {
                println("We caught the exception. Good.")
            }
    
            try {
                coroutineScope.async { println("we can still use the scope") }.await()
            } catch (e: IllegalStateException) {
                println("Why is this same exception still being thrown?")
            }
    
        }
    
    }
    

这是测试的输出:

We caught the exception. Good.
Why is this same exception still being thrown?
  • 为什么会这样?

    • 我的理解是您可以正常处理异常并使用协程从异常中恢复。
  • 我应该如何处理异常?

    • 我需要创建一个新的 coroutineScope 吗?
    • 如果我想继续使用同一个 coroutineScope,我可以从不抛出异常吗?
    • 我应该返回Either<Result, Exception>吗?
    • 我尝试过使用 CoroutineExceptionHandler,但仍然得到相同的结果。

注意我使用的是 Kotlin 1.3

【问题讨论】:

  • 通过查看 CoroutineScope javadoc:“取消或失败或此范围内的任何子协程都会取消所有其他子协程”。所以也许我需要为每个调用创建一个新的范围。
  • SupervisorJob 用作parentJob

标签: kotlin error-handling kotlin-coroutines


【解决方案1】:

当您在作用域中启动协程时(使用asynclaunch),默认情况下协程失败会取消此作用域以立即取消所有其他子项。这种设计避免了悬空和丢失的异常。

这里的一般建议是:

  • 除非你真的需要并发,否则不要使用async/await。当您使用挂起函数设计代码时,没有太多需要使用asyncawait

  • 如果确实需要并发执行,请遵循以下模式:

    coroutineScope { 
        val d1 = async { doOne() }
        val d2 = async { doTwo() }
        ...
        // retrieve and process results
        process(d1.await(), d2.await(), .... )
    }
    

如果您需要处理并发操作的失败,请将try { ... } catch { ... } 放在coroutineScope { ... } 周围,以捕获任何并发执行操作中的失败。

【讨论】:

  • 只是为了完全确定,如果我用 try/catch 包装对挂起函数的调用并因此捕获异常,它会取消范围和其他子项吗?换句话说,在父协程取消方面,捕获的异常与未捕获的异常有区别吗?
猜你喜欢
  • 2021-04-22
  • 2019-06-22
  • 2022-07-05
  • 2020-10-22
  • 2021-02-01
  • 1970-01-01
  • 2019-04-27
  • 1970-01-01
  • 2016-06-28
相关资源
最近更新 更多