Description
Copying from #4147 (comment):
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.