nikialeksey
01/09/2024, 9:27 PMimport kotlinx.coroutines.*
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, e ->
println("Exception from handler: " + e)
}
val scope = CoroutineScope(Dispatchers.Default + handler)
val launchJob = scope.launch {
supervisorScope {
launch {
println("Long running task...")
delay(500)
if (true) {
throw Exception("Oops!")
}
}
launch {
delay(1000)
println("Another child")
}
}
}
launchJob.join()
println("Application is not crashed.")
}
second:
import kotlinx.coroutines.*
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, e ->
println("Exception from handler: " + e)
}
val scope = CoroutineScope(Dispatchers.Default + handler)
val launchJob = scope.launch {
supervisorScope {
async {
println("Long running task...")
delay(500)
if (true) {
throw Exception("Oops!")
}
}
launch {
delay(1000)
println("Another child")
}
}
}
launchJob.join()
println("Application is not crashed.")
}
First output:
Long running task...
Exception from handler: java.lang.Exception: Oops!
Another child
Application is not crashed.
Second output:
Long running task...
Another child
Application is not crashed.
Could you please explain me why CoroutineExceptionHandler did not catch exception in case when we use async inside supervisorScope?Joffrey
01/09/2024, 9:30 PMasync
you're supposed to await the result (explicitly with await
) at some point, and that's probably where you want the exception to be thrown. I think this is confusing not only to you, IIRC there was an open issue about exception handling with asyncPeter Farlow
01/09/2024, 9:45 PMA coroutine that was created using async always catches all its exceptions and represents them in the resulting Deferred object, so it cannot result in uncaught exceptions.https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/
nikialeksey
01/09/2024, 9:50 PMimport kotlinx.coroutines.*
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, e ->
println("Exception from handler: " + e)
}
val scope = CoroutineScope(Dispatchers.Default + handler)
val launchJob = scope.launch {
coroutineScope {
async {
println("Long running task...")
delay(500)
if (true) {
throw Exception("Oops!")
}
}
launch {
delay(1000)
println("Another child")
}
}
}
launchJob.join()
println("Application is not crashed.")
}
Output will be:
Long running task...
Exception from handler: java.lang.Exception: Oops!
Application is not crashed.
So async propogated his exception to parent scope (coroutineScope) without await!Sam
01/10/2024, 8:00 AMasync
works. When it's a root coroutine, or a child of a supervisor job, it swallows exceptions. In all other cases, it propagates them to the parent job, regardless of whether await
is called. I don't think there's an "explanation", other than the series of design choices that led us here. Since this behaviour changed with the introduction of structured concurrency, I suppose some of the documentation for it might be out of date or confusing.nikialeksey
01/10/2024, 7:49 PMnikialeksey
01/11/2024, 8:01 PM