Jordi Pradel
10/06/2020, 1:20 PMasync()
exposes users to exceptions:
Coroutine builders come in two flavors: propagating exceptions automatically (launch and actor) or exposing them to users (async and produce).But then, I'd expect wrarping the
await()
call in try... catch
would just allow me to catch the exception produced in the async
block. Instead, it seems the async()
block's exception is handled by the supervisor. That gives me an odd behavior in this example:
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
try {
val res = async {
// suspend fun that ends up throwing ArithmeticException
failedConcurrentSum()
}
res.await()
} catch(e: ArithmeticException) {
println("Computation failed with ArithmeticException")
}
}
The odd part is that the exception is caught by catch
but, at the same time, it is thrown by runBloking.
See an executable snippet here: https://pl.kotl.in/sWjfVuEDr.
Why is that so? What am I missing here??bezrukov
10/06/2020, 1:30 PMcoroutineScope
builder:
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> { // Scope1
try {
coroutineScope { // Scope2
val res = async {
// suspend fun that ends up throwing ArithmeticException
failedConcurrentSum()
}
res.await()
}
} catch(e: ArithmeticException) {
println("Computation failed with ArithmeticException")
}
}
In this case, when error happens in async, it only cancels Scope2
. Then exception propagated to scope1, but you catch itbezrukov
10/06/2020, 1:31 PMJordi Pradel
10/06/2020, 1:32 PMrunBlocking
? I'd expect async to automatically create such a coroutineScope for me, so that if I catch whatever it throws upon await, I don't get it kind of "duplicated".Jordi Pradel
10/06/2020, 1:59 PMbezrukov
10/06/2020, 2:04 PMI'd expect async to automatically create such a coroutineScope for me, so that if I catch whatever it throws upon await, I don't get it kind of "duplicated".it's done this way to handle this:
coroutineScope {
val a = async { longOperation() }
val b = async { shortButFailedOperation() }
doSmth(a.await(), b.await())
}
If the error was propagated only when you call .await()
the code above would wait for completion of a
despite it doesn't make sense. Currently once b
failed, coroutineScope
failed as well, so it cancels a
.bezrukov
10/06/2020, 2:05 PMsync
, because coroutineScope waits for all childrenbezrukov
10/06/2020, 2:06 PMval a = safeAsync { doSmth() }
val b = safeAsync { doSmthElse() }
doSmthElse
will be executed strictly after completion of doSmth
.Jordi Pradel
10/06/2020, 3:00 PMJordi Pradel
10/07/2020, 11:23 AMJordi Pradel
10/07/2020, 11:27 AMIf the error was propagated only when you callIn that example, let's say I handle an error in the first async atthe code above would wait for completion of.await()
despite it doesn't make sense. Currently oncea
failed,b
failed as well, so it cancelscoroutineScope
.a
await
time. I understand that is not how this is designed to work, but it would be much more clear to me if that avoided b to be cancelled:
coroutineScope {
val a = async { longOperation() }
val b = async { shortButFailedOperation() }
val actualA = try { a.await() } catch (_: Exception) { 23 }
doSmth(actualA, b.await())
}
Jordi Pradel
10/07/2020, 11:29 AMa.await()
)is something I may need to do in `doSmth`instead. And doSmth
doesn't contain the actual code used to calculate the Deferred
it receives as an argument.Jordi Pradel
10/07/2020, 11:31 AMasync
to not cancel anything until someone `await`s on that and an exception is thrown.bezrukov
10/07/2020, 11:35 AMbezrukov
10/07/2020, 11:36 AMcoroutineScope
to supervisorScope
you will see desired behaviorJordi Pradel
10/07/2020, 11:37 AMbezrukov
10/07/2020, 11:37 AMsupervisorScope
child cancellation doesn't affect other children. So in supervisor scope exception will be propagated on await()
callJordi Pradel
10/07/2020, 11:37 AMb
would not cancel anything on failure, because it returned a Deferred that would carry its resultJordi Pradel
10/07/2020, 11:38 AM