Variation of `lazy` taking a timeout such that if ...
# codereview
m
Variation of
lazy
taking a timeout such that if the
initializer
takes more than
timeMillis
to execute it will return null until the point when
initializer
returns a value, at which point it will return that value. This relies on the fact the
initializer
continues to run after the timeout is reached.
Copy code
fun <T> lazyWithTimeout(timeMillis: Long, lock: Any? = null, initializer: () -> T): Lazy<T?> = LazyWithTimeout(initializer, timeMillis, lock)

private class LazyWithTimeout<out T>(initializer: () -> T, timeMillis: Long, lock: Any? = null): Lazy<T?> {
    private var calculatedValue: T? = null

    private val valueOfLazy by lazy(lock) {
        executeWithTimeoutOrNull(timeMillis) {
            initializer().also {
                calculatedValue = it
            }
        }
    }

    override val value: T?
        get() = calculatedValue ?: valueOfLazy

    override fun isInitialized(): Boolean = calculatedValue != null
}

fun <R> executeWithTimeoutOrNull(
    timeMillis: Long,
    dispatcher: CoroutineDispatcher = <http://Dispatchers.IO|Dispatchers.IO>,
    block: () -> R,
): R? = runBlocking {
    withTimeoutOrNull(timeMillis) {
        runInterruptible(dispatcher, block)
    }
}
e
it would be simpler to
Copy code
val deferredValue by lazy {
    GlobalScope.async {
        initializer()
    }
}

suspend fun getValue(timeMillis: Long) =
    withTimeoutOrNull(timeMillis) { deferredValue.await() }
m
Is there a performance overhead with executing
withTimeoutOrNull
each time?
e
exposing something with
runBlocking
is a bad pattern - it can interact poorly with any code that is attempting to use it from
suspend
context
and personally I'd prefer that the API expose the
Deferred
directly and let consumers decide how they want to consume it - with
if (isCompleted) getCompleted()
if they want a non-blocking result,
await()
if they can wait, etc.
m
Ok, so then it’s not possible to implement
Lazy
in such a way
e
yes. https://kotlinlang.org/docs/coding-conventions.html#functions-vs-properties I would also say that a property that changes itself is not very expected
m
Yes, I agree using
Deferred
directly is much cleaner. Is there any reason to use
lazy
instead of
start = CoroutineStart.LAZY
?
e
no reason beyond I didn't think of it at the time 😛