Hi,
I've just noticed that using new structural coroutines, the code which uses async(start = CoroutineStart.LAZY) { } causes infinite suspend.
As I understand these async(start = CoroutineStart.LAZY) is now treated as a children of TestView object and new coroutine engine waits until these async completes. Shouldn't LAZY async coroutines be excluded from a wait list if they are not running?
Maybe there are more proper way to implement lazy async val?
test example:
class TestView(override val coroutineContext: CoroutineContext) : CoroutineScope {
val title: Deferred<String> = async(start = CoroutineStart.LAZY) { "demo" }
val description: Deferred<String> = async(start = CoroutineStart.LAZY) { "desc" }
}
class TestClass {
suspend fun getView(id: Int): TestView = coroutineScope { TestView(coroutineContext = coroutineContext) }
}
runBlocking {
val view = TestClass().getView(123)
}
As I understand these async(start = CoroutineStart.LAZY) is now treated as a children of TestView object and new coroutine engine waits until these async completes
Right.
Shouldn't LAZY async coroutines be excluded from a wait list if they are not running?
Not really, for a couple of reasons. Firstly, it will make scoping rules non-trivial. Instead of "All coroutines launched from the scope are part of this scope" it will be "If ..., otherwise ...". Mental model with a lot of corner cases are proven to be less understandable and more error-prone, and we definitely don't want that. Secondly, there will be data race by design between the first call to await and end of the wait list, which adds non-determinism to lazy values.
Maybe there are more proper way to implement lazy async val?
Depends on what you want. Both GlobalScope.async(start = LAZY) and by lazy { async {} } are fine, but have slightly different semantics.
Thanks for your response. Actually the problem is simple, we have some kind of "View" object which has many properties those are interdependent example:
Pseudo code:
class View(today:Date) {
val rawLogs = database.fetch(interval=today)
val onlyAuthorizations = rawLogs.filter(it.type == Auth)
val signatures = rawLog.filter(it.type == Signature)
val signatureCount = signatures.length
}
Problem is that rawLogs is expensive operation and I don't want to call it unless it's needed, also I want to cache a value and run fetch only once per "View" object. It's like some kind of lazy report which evaluates when accessing object's property.
If these operations wouldn't be on coroutines when I just would use "by lazy", now I don't know. Maybe convert everything to suspend functions or something. Maybe there will be support for by lazy with coroutines?
Core library should include Deferred.getValue extension method.
We can consider by async as a good replacement of by lazy, and we should consider code like:
fun getLog() = coroutineScope {
val logs by async(start = LAZY) { TODO() } // <-- GlobalScope?
...
}
As I've mention previously, both GlobalScope.async(start = LAZY) and by lazy { async {} } are fine, depending on your cancellation pattern.
If rawLogs computation is pure IO and does not suspend internally, it is ok to use GlobalScope variant, otherwise by lazy may be more robust.
When using by lazy { async {} }, which CoroutineScope are we supposed to use?
My current code is the following, does it look right to you?
private class SuspendLazySuspendingImpl<out T>(
private val dispatcher: CoroutineDispatcher = Dispatchers.Default,
initializer: suspend () -> T
) {
private val lazyValue = lazy { GlobalScope.async(dispatcher) { initializer() }}
suspend operator fun invoke(): T = with(lazyValue) {
if (isInitialized()) value else withContext(dispatcher) { value }
}.await()
}
Not sure why this is closed, this shouldn't break randomly depending on how you write the async.
This suspends forever:
withContext(Dispatchers.IO) {
async(start= LAZY) {}
}
This works fine:
GlobalScope.async(Dispatchers.IO, start= LAZY) { }
There should not be magic like this - esp considering this has been a problem for nearly two years.
Hi, @chrisjenx,
You can consider #1065