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

Kotlin 当您在协程范围内抛出异常时,协程范围是否可重用?,kotlin,error-handling,kotlin-coroutines,Kotlin,Error Handling,Kotlin Coroutines,通过以下步骤,我在解决协同程序的错误处理方面遇到了问题,我将其缩小到本单元测试中: 我创建了一个与任何调度器的协同路由作用域 我在异步块(甚至在嵌套的异步块)的这个范围内的任何地方抛出异常 我对返回的延迟值调用wait并处理异常 这一切都很好。但是,当我尝试使用相同的协同程序作用域来启动新的协同程序时,这总是在异常情况下完成,并且出现相同的异常 以下是测试: fun `when you throw an exception in a coroutine scope, is the corouti

通过以下步骤,我在解决协同程序的错误处理方面遇到了问题,我将其缩小到本单元测试中:

  • 我创建了一个与任何调度器的协同路由作用域
  • 我在异步块(甚至在嵌套的异步块)的这个范围内的任何地方抛出异常
  • 我对返回的延迟值调用wait并处理异常
  • 这一切都很好。但是,当我尝试使用相同的协同程序作用域来启动新的协同程序时,这总是在异常情况下完成,并且出现相同的异常

    以下是测试:

    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?
    
  • 以下是测试的输出:

    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,我是否可以从不抛出异常
      • 我是否应该返回
        
        
      • 我尝试过使用CoroutineExceptionHandler,但仍然得到相同的结果

    注意:我使用的是Kotlin 1.3,当您在一个作用域中启动一个协同程序(使用
    async
    launch
    )时,默认情况下,协同程序失败将取消该作用域,以立即取消所有其他子项。这种设计避免了悬空和丢失异常

    这里的一般建议是:

    • 除非确实需要并发,否则不要使用
      async
      /
      wait
      。当您使用挂起函数设计代码时,不需要太多地使用
      async
      wait

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

      coroutineScope { 
          val d1 = async { doOne() }
          val d2 = async { doTwo() }
          ...
          // retrieve and process results
          process(d1.await(), d2.await(), .... )
      }
      
    如果您需要处理并发操作的失败,那么将
    try{…}catch{…}
    放在
    coroutineScope{…}
    周围,以捕获任何并发执行操作中的失败

    • 还有其他高级机制(如
      SupervisorJob
      )允许细粒度异常处理。您可以在文档中阅读更多内容

    查看协同作用域javadoc:“取消或失败或此作用域中的任何子协同作用域将取消所有其他子项”。因此,可能我需要为每个调用创建一个新的作用域。使用as
    parentJob
    。为了完全确定,如果我使用try/catch将调用包装到一个挂起函数,从而捕获一个异常,它会取消作用域和其他子项吗?换句话说,捕获的异常在父协同程序取消方面是否与未捕获的不同?