Mark
05/03/2021, 3:53 AMCompletableDeferred
or something else?
// from this
val someProperty: Int by lazy {
// refactoring to perform some suspendable logic
}
// to this
suspend fun someProperty(): Int {
// only should be executed max once (and then cached)
}
fun <T> suspendableLazy(scope: CoroutineScope, provider: suspend () -> T) = object : SuspendableProvider<T> {
private val computed = scope.async(start = CoroutineStart.LAZY) { provider() }
override val isCompleted: Boolean
get() = computed.isCompleted
override suspend fun get() = computed.await()
}
But then I end up passing some application-scope (e.g. GlobalScope
). Wouldn’t it be better to use a scope of the coroutine calling the function for the first time?ephemient
05/03/2021, 4:30 AMMark
05/03/2021, 4:31 AMephemient
05/03/2021, 4:32 AMfun <T : Any> suspendableLazy(provider: suspend () -> T) = object : SuspendableProvider<T> {
private val mutex = Mutex()
private var result: T? = null
override val isCompleted: Boolean get() = result != null
override suspend fun get() = mutex.withLock {
result ?: provider().also { result = it }
}
}
Mark
05/03/2021, 4:35 AMdeferredProperty.await()
private class CachingSuspendableProvider<T>(private val provider: suspend () -> T) : SuspendableProvider<T> {
private var result: T? = null
private val mutex = Mutex()
override suspend fun get(): T = result
?: mutex.withLock {
result ?: provider().also {
result = it
}
}
override val isCompleted: Boolean
get() = result != null
}
private class SuspendableLazy<T>(provider: suspend () -> T): Lazy<suspend () -> T> {
private val suspendableLazy = CachingSuspendableProvider(provider)
override val value: suspend () -> T
get() = suspendableLazy::get
override fun isInitialized() = suspendableLazy.isCompleted
}
fun <T> suspendableLazy(provider: suspend () -> T): Lazy<suspend () -> T> = SuspendableLazy(provider)
// usage
private val fooDelegate by suspendableLazy { /* compute Foo */ }
suspend fun getFoo(): Foo = fooDelegate()
ephemient
05/03/2021, 5:10 AMval lazyValue = suspendableLazy { compute() }::get
with the previous implementation, and less ceremonyOliver.O
05/04/2021, 1:09 PMsuspendableLazy
contains an unsynchronized read on `result`:
override val isCompleted: Boolean get() = result != null
Wouldn't that require annotating result
with @Volatile
?ephemient
05/04/2021, 3:55 PMif (isComplete) get()
? it's going to wait on the mutex for the value no matter whatOliver.O
05/04/2021, 9:20 PMisCompleted
for.
Anyway, I don't really see it working:
val lazyValue = suspendableLazy { compute() }::get
returns a function, not a value.ephemient
05/04/2021, 9:44 PM