问题
I've been having problems figuring out error handling with coroutines that I've narrowed down to this unit test with the following steps:
- I create a coroutine scope, with any dispatcher.
- I throw an exception anywhere within this scope in an async block (or even in a nested async block).
- I call await on the returned deferred value and handle the exception.
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 useasync
andawait
.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