Reputation: 6187
Given the code snippets below:
Snippet [1]
val job = SupervisorJob()
val scope = CoroutineScope(Dispatchers.IO + job)
scope.launch {
try {
scope.async {
throw RuntimeException("Oops!")
}.await()
} catch(e: Exception) {
// Handle exception
}
}
and Snippet [2]
val job = SupervisorJob()
val scope = CoroutineScope(Dispatchers.IO + job)
scope.launch {
try {
async {
throw RuntimeException("Oops!")
}.await()
} catch(e: Exception) {
// Handle exception
}
}
The first one works, and second snippet crashes. The general explanation is that in the first case, async
inherit the SupervisorJob
from scope
, and in the second case no.
My question is, if async
is an extension function of CoroutineScope
, why in the second case (which crashes) it doesn't inherit the SupervisorJob
in the same way?
Upvotes: 4
Views: 918
Reputation: 474
As I see, newer versions of coroutines both of your snippets will work. Just wrap the function await()
with try/catch block.
Upvotes: 0
Reputation: 200206
In your first snippet, because you explicitly override the scope established by the launch
builder, there is no parent-child relationship between it and the async
block you have inside it.
This is an equivalent way to write the first snippet:
val deferred = scope.async {
throw RuntimeException()
}
scope.launch {
try {
deferred.await()
} catch(e: Exception) {
}
}
The async
coroutine fails with an exception. Its parent is a SupervisorJob
, which ignores the failure. The launch
coroutine calls Deferred.await()
, which throws an exception. You catch the exception and nothing fails.
Your second snippet can be rewritten as follows:
scope.launch {
val innerScope = this
innerScope.async {
throw RuntimeException()
}
}
Here you can see explicitly which scope the async
block inherits. It is not the top-level one with SupervisorJob
, but the inner one that launch
created. When the async
coroutine fails, the parent job reacts to it by first cancelling all the other children (none in this case) and then itself. The deferred.await()
statement makes no difference here, which is why I removed it. The launch
coroutine will automatically wait for all the child coroutines to complete anyway.
Upvotes: 2
Reputation: 59253
launch
creates a new Job, and propagates that into the CoroutineScope
that the block receives. In the second snippet, the async
job is a child of the launch
Job, and the launch
job is cancelled when its child fails.
In the first snippet, the async
job is a child of the SupervisorJob
you made, which is not cancelled when its child fails. The launch
job in this case has no children. It just catches the exception and completes.
Upvotes: 2