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.