mat
06/24/2024, 6:09 PMclass RefreshableObject<T : Any>(
private val scope: CoroutineScope,
private val getter: suspend () -> T,
private val getOnInit: Boolean = true,
private var inner: T? = null,
private var getInnerJob: Job? = null,
) {
init {
if (getOnInit) needsToBeRefreshed()
}
fun get(): T {
if (inner != null) return inner!!
if (getInnerJob == null) needsToBeRefreshed()
runBlocking {
getInnerJob!!.join()
}
return inner!!
}
// Ensures that the object is eventually refreshed, calls to get after this may still receive the old object.
fun needsToBeRefreshed() {
getInnerJob = scope.launch {
inner = getter()
}
}
}
Awkin
06/24/2024, 7:52 PMDoes joining a blocked job never end?Yes,
join
only returns when the job completes.
You didn't ask for this, but here are some ways to avoid null
checks:
class RefreshableObject<T : Any>(
private val scope: CoroutineScope,
private val getter: suspend () -> T,
private val getOnInit: Boolean = true,
private var inner: T? = null,
private var getInnerDeferred: Deferred<T>? = null,
) {
init {
if (getOnInit) needsToBeRefreshed()
}
fun get(): T {
inner?.let { return it }
if (getInnerDeferred == null) needsToBeRefreshed()
return runBlocking {
getInnerDeferred!!.await()
}
}
// Ensures that the object is eventually refreshed, calls to get after this may still receive the old object.
fun needsToBeRefreshed() {
getInnerDeferred = scope.async {
getter().also {
inner = it
}
}
}
}
If you change Job
to a Deferred
like that, you can avoid storing inner
at all, as Deferred
will deal with that anyway:
class RefreshableObject<T : Any>(
private val scope: CoroutineScope,
private val getter: suspend () -> T,
getOnInit: Boolean = true, // doesn't need to be a `val`
private var getInnerDeferred: Deferred<T>? = null,
) {
init {
if (getOnInit) needsToBeRefreshed()
}
fun get(): T {
if (getInnerDeferred == null) needsToBeRefreshed()
return runBlocking {
getInnerDeferred!!.await()
}
}
// Ensures that the object is eventually refreshed, calls to get after this may still receive the old object.
fun needsToBeRefreshed() {
getInnerDeferred = scope.async {
getter()
}
}
}
The last !!
can be removed like this:
class RefreshableObject<T : Any>(
private val scope: CoroutineScope,
private val getter: suspend () -> T,
getOnInit: Boolean = true,
private var getInnerDeferred: Deferred<T>? = null,
) {
init {
if (getOnInit) needsToBeRefreshed()
}
fun get(): T {
val deferred = getInnerDeferred ?: refresh()
return runBlocking {
deferred.await()
}
}
private fun refresh(): Deferred<T> = scope.async {
getter()
}
// Ensures that the object is eventually refreshed, calls to get after this may still receive the old object.
fun needsToBeRefreshed() {
getInnerDeferred = refresh()
}
}
Awkin
06/24/2024, 7:52 PMget
not a suspend
function?mat
06/24/2024, 7:53 PMmat
06/24/2024, 7:53 PMmat
06/24/2024, 7:56 PMmat
06/24/2024, 7:56 PMmat
06/24/2024, 7:56 PMmat
06/24/2024, 7:59 PMDaniel Pitts
06/25/2024, 2:33 AMget
method isn't suspend, it will have block until something is ready.