kotlinx.coroutines
kotlinx.coroutines copied to clipboard
Consider discouraging `CoroutineStart.LAZY`
Copying from https://github.com/Kotlin/kotlinx.coroutines/pull/4147#discussion_r1696736494:
How does one use LAZY? Typically, in a pattern like
// a property
val optionallyRequiredValue = scope.async(start = CoroutineStart.LAZY) {
// ...
}
But I don't see how this is better than
val optionallyRequiredValue by lazy {
scope.async {
// ...
}
}
The latter option doesn't spawn unnecessary new child coroutines (which could stop the scope from completing if you aren't careful).
Or let's say you have something similar to
val sendOnes = scope.launch(start = CoroutineStart.LAZY) {
while (true) { onesChannel.send(1) }
}
Then, you have to write things like sendOnes.start(); channel.receive() throughout your codebase. But how is this better than something like the following?
val onesChannel by lazy {
Channel<Int>().also {
scope.launch {
while (true) { onesChannel.send(1) }
}
}
}
I can sort of see the utility of LAZY when you have to combine on-demand computation with the ability to reinitialize the variable anew:
@Volatile // or @Synchronized
var latestResult: Deferred<Int> = computeResult()
private fun computeResult(): Deferred<Int> = scope.async(start = CoroutineStart.LAZY) {
// ...
}
fun update() {
// latestResult?.let { it.cancel() } // can't do that: the clients will throw
latestResult = computeResult()
}
But this looks to me like a good fit for a hot Flow chain: mutable state in concurrent scenarios is tricky to get right, it's best avoided, not to mention that this allows for many parallel computeResult() computations, which can DDoS the system.
I'm really struggling to think of a use case where CoroutineStart.LAZY is actually the superior option. When I search grep.app for LAZY usages, most of them are completely unclear to me and seem to have been created by accident, like https://github.com/Baeldung/kotlin-tutorials/blob/f203e8fd8571b8dcc313fca04c72196bc4948649/spring-boot-kotlin/src/main/kotlin/com/baeldung/nonblockingcoroutines/controller/ProductControllerCoroutines.kt#L31-L44 Why is this LAZY if it's immediately started?
https://github.com/JetBrains/compose-multiplatform/blob/6aad20ec087f54df168ad7e7ce03c4ded710a93c/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/AsyncCache.kt could be a mistake, or it could actually be dictated by some requirement to avoid deadlocks if CoroutineDispatcher.dispatch calls getOrLoad. I'm not sure.
The existence of LAZY led people to writing code that's more difficult to follow than the alternative without it. If LAZY really is a situationally useful thing at best, we could mark it as Delicate. If it is useless, we can deprecate it. For now, we can edit the docs, but in general, we should deliberately research how it's used in some larger codebases.
I completely agree. I've been bitten more than once by a coroutineScope refusing to return because an async(start = LAZY) was never started.
One downside to using lazy is the synchronization it does. While this can be avoided using the argument, it's not obvious that it's required.