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

限于喜欢 提交于 2020-02-04 05:09:05

问题


I've been having problems figuring out error handling with coroutines that I've narrowed down to this unit test with the following steps:

  1. I create a coroutine scope, with any dispatcher.
  2. I throw an exception anywhere within this scope in an async block (or even in a nested async block).
  3. I call await on the returned deferred value and handle the exception.
  4. This is all fine. However, when I try to use the same coroutine scope to launch a new coroutine, this always completes exceptionally with the same exception.

    Here is the test:

    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?")
            }
    
        }
    
    }
    

Here is the output of the test:

We caught the exception. Good.
Why is this same exception still being thrown?
  • Why is this happening?

    • My understanding was that you could handle exceptions normally and recover from them with coroutines.
  • How should I deal with exceptions?

    • Do I need to create a new coroutineScope?
    • Can I never throw exceptions if I want to keep using the same coroutineScope?
    • Should I return Either<Result, Exception>?
    • I've tried using CoroutineExceptionHandler but I still get the same results.

Note I'm using Kotlin 1.3


回答1:


When you start a coroutine in a scope (using either async or launch), then a failure of a coroutine by default cancels this scope to promptly cancel all the other children. This design avoid dangling and lost exceptions.

The general advice here is:

  • Don't use async/await unless you really need concurrency. When you design your code with suspending functions there is no much need to use async and await.

  • If you do need concurrent execution, then follow the pattern:

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

If you need to handle a failure of a concurrent operation, then put try { ... } catch { ... } around coroutineScope { ... } to catch a failure in any of the concurrently executing operations.

  • There are additional advanced mechanisms (like SupervisorJob) that allow fine-grained exception handling. You can read more in the documentation https://kotlinlang.org/docs/reference/coroutines/exception-handling.html


来源:https://stackoverflow.com/questions/53549113/when-you-throw-an-exception-in-a-coroutine-scope-is-the-coroutine-scope-reusabl

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!