当你在协程范围内抛出异常时,协程范围是否可重用?

When you throw an exception in a coroutine scope, is the coroutine scope reusable?

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

  1. 我用任何调度程序创建了一个协程作用域。
  2. 我在异步块(甚至嵌套异步块)中此范围内的任何位置抛出异常。
  3. 我在 returned 延迟值上调用 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?

注意我使用的是 Kotlin 1.3

当您在一个范围内启动协程时(使用 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 { ... } 周围以捕获任何并发执行操作的失败。