Nguyễn Nhật Tân
03/10/2025, 7:44 AMfun main(): Unit = runBlocking {
val scope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO> + SupervisorJob())
scope.launch {
delay(1000)
throw Error("Child 1 failed")
}
scope.launch {
delay(1500)
println("Child 2 Success")
}
// Wait
delay(3000)
}
Output:
Exception in thread "DefaultDispatcher-worker-3 @coroutine#2" java.lang.Error: Child 1 failed
//stacktrace
Child 2 Success
Observation:
Child 2 is not canceled, the program continues running normally and just logs the stack trace.
Second code snippet:
viewModelScope.launch {
delay(1000)
throw Error("Child 1 failed")
}
viewModelScope.launch {
delay(1500)
println("Child 2 Success")
}
Output:
FATAL EXCEPTION: main
Process: com.example.myapplication, PID: 6877
java.lang.Error: Child 1 failed
at...
Observation:
The line "Child 2 Success"
is not printed.
The application crashes, terminating the whole process.
I understand that in the second code snippet, the exception propagates to the parent coroutine. Since Android’s default exception handler crashes the app, the whole process is terminated.
Does this mean that, by default in Android, we do not benefit from SupervisorJob
at all?
We still need to handle exceptions using CoroutineExceptionHandler
or a try-catch
block?
Kotlin Coroutine documentation provides examples that are suitable for regular Java/Kotlin programs, but they don’t apply the same way in Android. This can be misleading and confusing for developers.Dmitry Khalanskiy [JB]
03/10/2025, 10:46 AMDoes this mean that, by default in Android, we do not benefit fromIt does not mean that.at all?SupervisorJob
SupervisorJob
can ensure that coroutines won't get cancelled if other coroutines in the same scope fail. Whether the program itself will crash is another question.
We still need to handle exceptions usingYes, if a coroutine is `launch`'ed (instead of being created withor aCoroutineExceptionHandler
block?try-catch
async
), it will attempt to notify its parent about the failure, but if the parent is not interested (that is, is a supervisor), then it will use the CoroutineExceptionHandler
, or if there is none, the default one for the platform.
Kotlin Coroutine documentation provides examples that are suitable for regular Java/Kotlin programsCould you please provide links to these examples?
louiscad
04/10/2025, 10:49 PMval scope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO> + SupervisorJob())
Could lead to coroutines being garbage collected while suspended.
You want to keep the scope in a member or top-level strong reference, and you might just need to create a local scope with supervisorScope { … }
if you want to not rethrow child exceptions/throwables.Dmitry Khalanskiy [JB]
04/11/2025, 7:01 AMCould lead to coroutines being garbage collected while suspended.I think you're talking about https://github.com/Kotlin/kotlinx.coroutines/issues/1061, but for this to be a problem, you need someone who would want to potentially wake up a coroutine to store it as a weak reference.
louiscad
04/12/2025, 9:17 AMMediaPlayer
instance, which is only a local property (so only a cycle reference between the ContinuationImpl
and MediaPlayer
), and the system keeps the MediaPlayer
instance as a weak reference, so the callback that references the continuation has no stable root, and when the GC comes, only a strongle referenced Job
(via a strongly referenced scope) will prevent it from disappearing silently while suspended.
Any API can do this kind of thing, directly, or indirectly.
The best thing to do is to make sure any custom CoroutineScope
is strongly referenced somewhere, so you don't need to check all the suspending functions that might be called within that scope (which you might not control!)louiscad
04/12/2025, 9:17 AMGlobalScope
(since it's never keeping any reference to the child coroutines, effectively making them all orphans, potentially subject to kidnapping by the GC)Dmitry Khalanskiy [JB]
04/14/2025, 7:24 AMAny API can do this kind of thing, directly, or indirectly.That also means you can't directly pass lambdas/anonymous classes to this API at all, as those are not strongly referenced, which makes the API error-prone. Every user of
MediaPlayer
has to be aware of this, even if coroutines manage to prevent some portion of the problems. I believe the cleaner solution is to work around this on the call site of the problematic API, regardless of whether coroutines are used at all: https://github.com/Kotlin/kotlinx.coroutines/issues/1061#issuecomment-2796523272louiscad
04/14/2025, 12:54 PMDmitry Khalanskiy [JB]
04/14/2025, 1:05 PMMediaPlayer
to inevitably encounter this issue eventually. Having every user of coroutines pay the price of measurably increased memory consumption for shielding the users of MediaPlayer
some of the time (and not even needing our protection any more after learning the gotcha) doesn't feel feasible to me.louiscad
04/14/2025, 2:53 PMDmitry Khalanskiy [JB]
04/14/2025, 3:00 PMSharedFlow
update gets GC'd if the SharedFlow
itself gets GC'd.louiscad
04/14/2025, 3:02 PMlouiscad
04/14/2025, 3:02 PMDmitry Khalanskiy [JB]
04/14/2025, 3:05 PMCoroutineScope()
inside a function is error-prone: coroutine scopes created like this are purely for storing in object fields and should be cancelled when the object is no longer needed.