I am trying to run multiple tasks in parallel, but the tasks are independent so if one of children coroutine fails, I do not want its siblings or parent to fail too. In the code below I have used coroutineScope to create a new scope in which these tasks run and launched 5 async tasks each on sending its id and the delay time it should wait. The second coroutine throws an exception. In this case the code does what i want it to do, it calculates the sum of the jobs that successfully finished and the ones that failed return 0.
However, I am reading that there is also supervisorScope in the kotlinx library which should be preferred instead of coroutineScope (which cancels parent/siblings if exception is not handled) for tasks are not dependent on others. I am not sure why I should be changing to use supervisorScope as I am getting the result I want with coroutineScope.
Q1: If i were to change to supervisorScope should something change in my async blocks?
Q2: Is it accepted to catch any exception inside the async block and dont let anything be propagated to its parent? I know you can also catch exceptions during .await() phase but is that the way it should be done?
runBlocking {
coroutineScope {
val job1 = async<Int> {
try {
request(1, 1000)
} catch (e: Exception) {
println("Job 1 failed with $e")
0
}
}
val job2 = async<Int> {
try {
request(2, 2000)
throw Exception("cancelling Job 2")
} catch (e: Exception) {
println("Job 2 failed: $e")
0
}
}
val job3 = async {
try {
request(3, 3000)
} catch (e: Exception) {
println("Job 3 failed with $e")
0
}
}
val job4 = async {
try {
request(4, 4000)
} catch (e: Exception) {
println("Job 4 failed with $e")
0
}
}
val job5 = async {
try {
request(5, 5000)
} catch (e: Exception) {
println("Job 5 failed with $e")
0
}
}
val result = job1.await() + job2.await() + job3.await() + job4.await() + job5.await()
println(result.toString())
}
println("Finished")
}
suspend fun request(id: Int, time: Long): Int {
println("Job $id started")
delay(time)
println("Job $id finished")
return id
}
The reason all coroutines run to completion is that you catch the exception thrown by job 2 within job 2 itself, so it never propagates up the hierarchy of Jobs, so nothing happens.
However, if you remove that catch clause in job2, job[1-5] will always be canceled, independent of if you use coroutineScope or supervisorScope.
This is because job2.await() will throw the exception instead. Since this happens within the parent job of job[1-5] (i.e. within the top coroutineScope/supervisorScope), and since a failed/cancelled parent job always cancel child jobs, job[1-5] will also be cancelled.
A1: Use none of coroutineScope or supervisorScope, remove coroutineScope and put things directly under runBlocking.
A2: It certainly is allowed to catch exceptions within async { } to make sure it doesn't happen in .await() if it fits your use case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With