Mark
05/03/2021, 7:00 AMLazy
is to encourage the result of suspendableLazy()
to be used as a property (since it doesn’t make sense to use it otherwise):
interface SuspendableLazy<T>: Lazy<suspend () -> T>
fun <T> suspendableLazy(scope: CoroutineScope, provider: suspend () -> T) = object : SuspendableLazy<T> {
private val computed = scope.async(start = CoroutineStart.LAZY) { provider() }
override val value: suspend () -> T
get() = computed::await
override fun isInitialized() = computed.isCompleted
}
fun <T> suspendableLazy(provider: suspend () -> T) = object : SuspendableLazy<T> {
@Volatile // since we are using the double-checked pattern
private var computedLambda: (suspend () -> T)? = null
private val mutex = Mutex()
override val value: suspend () -> T
get() {
computedLambda?.also {
return it
}
return result@{
// because now we are inside the lambda being returned
computedLambda?.also {
return@result it()
}
mutex.withLock {
// because now we have the lock
computedLambda?.also {
return@withLock it()
}
provider().also { computedValue ->
computedLambda = { computedValue }
}
}
}
}
override fun isInitialized() = computedLambda != null
}
// usage
val fooDelegate by suspendableLazy { /* compute Foo */ }
suspend fun fooValue(): Foo = fooDelegate()
ephemient
05/03/2021, 12:57 PMfun <T> lazySuspendOnce(
scope: CoroutineScope,
block: suspend () -> T
): suspend () -> T = scope.async(start = CoroutineStart.LAZY) { block() }::await
fun <T: Any> lazySuspendOnce(
block: suspend () -> T
): suspend () -> T {
val mutex = Mutex()
var value: T? = null
return {
mutex.withLock {
value ?: block().also { value = it }
}
}
}
val lazyFoo = lazySuspendOnce { /* compute Foo */ }
suspend fun fooValue() = lazyFoo()
Mark
05/03/2021, 1:03 PMephemient
05/03/2021, 1:03 PMMark
05/03/2021, 1:06 PMephemient
05/03/2021, 1:10 PMvolatile
Mark
05/03/2021, 1:12 PMlateinit var
?ephemient
05/03/2021, 1:14 PMT?
. there's @kotlin.jvm.Volatile
but I don't think it works on localsMark
05/03/2021, 1:19 PMvar lambda: (suspend () -> T)? = computedLambda
in the getter and then reference that instead? That would also be updated when computedLambda is updatedoverride val value: suspend () -> T
get() {
var lambda: (suspend () -> T)? = computedLambda
lambda?.also {
return it
}
return result@{
// because now we are inside the lambda being returned
lambda?.also {
return@result it()
}
mutex.withLock {
// because now we have the lock
lambda?.also {
return@withLock it()
}
provider().also { computedValue ->
lambda = { computedValue }
computedLambda = lambda
}
}
}
}
ephemient
05/03/2021, 1:20 PMMark
05/03/2021, 1:21 PMephemient
05/03/2021, 1:23 PMMark
05/03/2021, 1:24 PM@Volatile
annotation for nowCompletableDeferred
instead of the computedLambda property?ephemient
05/03/2021, 1:57 PMuli
05/04/2021, 1:45 PMephemient
05/04/2021, 3:58 PMcompletableResult.complete(compute())
run concurrently may run multiple compute()
concurrently; of course only one ends up as the result.uli
05/04/2021, 6:51 PM