Mark
01/09/2023, 6:35 AMlazy
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.
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)
}
}
ephemient
01/09/2023, 6:55 AMval deferredValue by lazy {
GlobalScope.async {
initializer()
}
}
suspend fun getValue(timeMillis: Long) =
withTimeoutOrNull(timeMillis) { deferredValue.await() }
Mark
01/09/2023, 7:02 AMwithTimeoutOrNull
each time?ephemient
01/09/2023, 7:20 AMrunBlocking
is a bad pattern - it can interact poorly with any code that is attempting to use it from suspend
contextephemient
01/09/2023, 7:21 AMDeferred
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.Mark
01/09/2023, 7:23 AMLazy
in such a wayephemient
01/09/2023, 7:24 AMMark
01/09/2023, 8:27 AMDeferred
directly is much cleaner. Is there any reason to use lazy
instead of start = CoroutineStart.LAZY
?ephemient
01/09/2023, 8:27 AM