Bilagi
07/04/2024, 10:23 AMrunBlocking
, it threw an exception even though it is surrounded by a try-catch
block.
I want to know how runBlocking
works in this scenario and why this exception is thrown in runBlocking
but not in a regular Android project.
import kotlinx.coroutines.*
fun main() = runBlocking {
val deferredUser = async {
// Simulating an exception
throw Exception("Something went wrong!")
}
try {
deferredUser.await()
} catch (ex: Exception) {
}
}
However, when I tried a similar approach in my Android project, the exception is handled as expected:
private fun tryAndTest() {
viewModelScope.launch {
val deferredUser = getUser()
try {
deferredUser.await()
} catch (ex: Exception) {
Log.d("viewModel", ex.toString())
}
}
}
private fun getUser() = viewModelScope.async {
// Simulating an exception
throw Exception("Something went wrong!")
}
Stylianos Gakis
07/04/2024, 10:28 AMviewModelScope
from android's ViewModel is using a supervisorScope internally, which will catch those exceptions and not let it propagate further up https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycl[…]eViewModelScope&ss=androidx%2Fplatform%2Fframeworks%2Fsupport
In your runBlocking example you do not have a supervisorScope() to do the sameJacob
07/04/2024, 10:31 AMBilagi
07/04/2024, 10:35 AMSupervisorJob
?Stylianos Gakis
07/04/2024, 11:07 AMSupervisorJob()
indeed, my bad.
Exceptions inside async blocks is always tricky. I honestly don't know what the expected behavior is here along with runBlocking.ross_a
07/04/2024, 12:29 PMCaught exception: java.lang.Exception: Something went wrong!
Caught exception 2: java.lang.Exception: Something went wrong!
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
try {
runBlocking {
val deferredUser = async {
// Simulating an exception
throw Exception("Something went wrong!")
}
try {
deferredUser.await()
} catch (ex: Exception) {
println("Caught exception: $ex")
}
}
} catch (ex: Exception) {
println("Caught exception 2: $ex")
}
ross_a
07/04/2024, 12:30 PM.join()
to ensure all child jobs have completed
i.e. it is the equivalent of:
try {
runBlocking {
val deferredUser = async {
// Simulating an exception
throw Exception("Something went wrong!")
}
try {
deferredUser.await()
} catch (ex: Exception) {
println("Caught exception: $ex")
}
deferredUser.join()
}
} catch (ex: Exception) {
println("Caught exception 2: $ex")
}
Bilagi
07/04/2024, 12:38 PMrunBlocking
behaves differently, please let us know.ross_a
07/04/2024, 12:39 PMviewModelScope
has a SupervisorJob
attached to it, you can make runBlocking
behave the same way by attaching SupervisorJob
to its scope runBlocking(SupervisorJob()) {}
Bilagi
07/04/2024, 12:40 PMviewModelScope
has a SupervisorJob
thats clear to me
but is runBlocking
is little confusing wrt exceptionsross_a
07/04/2024, 12:42 PM.await()
which you are catching, and once again on the implicit join()
at the end (which you are not, but which is suppressed if you are attaching SupervisorJob
)ross_a
07/04/2024, 12:50 PMBilagi
07/04/2024, 12:59 PMrunBlocking {
val deferredUser = async(Job()) {
// Simulating an exception
throw Exception("Something went wrong!")
}
try {
deferredUser.await()
} catch (ex: Exception) {
println("Caught exception: $ex")
}
}
ross_a
07/04/2024, 1:03 PMJob()
which doesn't have a reference to the parent to pass the exceptions back up
Without specifying a Job it is implicitly created like this:
runBlocking {
val parentJob = coroutineContext[Job]
val deferredUser = async(Job(parent = parentJob)) {
// Simulating an exception
throw Exception("Something went wrong!")
}
try {
deferredUser.await()
} catch (ex: Exception) {
println("Caught exception: $ex")
}
}
ross_a
07/04/2024, 1:04 PMSupervisorJob
rather than manually specifying Jobs for the children because this way if the parent is cancelled, the children are also cancelledBilagi
07/04/2024, 1:08 PM