Mark
11/16/2019, 7:03 AMprivate lateinit var deferredFactory: Deferred<MyFactory>
suspend fun getMyFactory(): MyFactory = withContext(Dispatchers.Main) {
if (::deferredFactory.isInitialized) {
return@withContext deferredFactory.await()
}
val deferred = CompletableDeferred<MyFactory>().apply {
deferredFactory = this
}
createFactory().also {
deferred.complete(it)
}
}
private suspend fun createFactory() = withContext(Dispatchers.Default) {
// slow stuff
}
=== UPDATE ===
After all the comments, this is what we came up with:
private val deferredFactory = GlobalScope.async(start = CoroutineStart.LAZY) {
createFactory()
}
suspend fun getFactoryOrNull() =
try {
deferredFactory.await()
} catch (e: Exception) {
//loge("unable to create factory", e)
null
}
octylFractal
11/16/2019, 7:07 AMAnimesh Sahu
11/16/2019, 7:26 AMoctylFractal
11/16/2019, 7:27 AMoctylFractal
11/16/2019, 7:28 AMMark
11/16/2019, 8:17 AMoctylFractal
11/16/2019, 8:18 AMoctylFractal
11/16/2019, 8:22 AMMark
11/16/2019, 8:26 AMoctylFractal
11/16/2019, 8:28 AMisInitialized
. I would prefer having a null object in this case ( https://en.wikipedia.org/wiki/Null_object_pattern )Mark
11/16/2019, 8:33 AMwithContext(Dispatchers.Main)
is not really needed https://pl.kotl.in/ukLrMF8gYMark
11/16/2019, 9:02 AMDeferred.await()
is that it will rethrow an exception after CompletableDeferred.completeExceptionally
octylFractal
11/16/2019, 9:03 AMasync {}
would probably be best for creating the deferred if you want that behavioroctylFractal
11/16/2019, 9:05 AMcoroutineScope { async(Dispatchers.Default) { MyFactory().apply { init(); factory = this } }.apply { deferredFactory = this; await() }
octylFractal
11/16/2019, 9:06 AMapply
inside coroutineScope
, or it will happen too lateMark
11/16/2019, 9:14 AMcoroutineScope
block at the same time?octylFractal
11/16/2019, 9:15 AMMark
11/16/2019, 9:15 AMoctylFractal
11/16/2019, 9:17 AMoctylFractal
11/16/2019, 9:18 AMuli
11/16/2019, 9:39 AMMark
11/16/2019, 9:47 AMuli
11/16/2019, 10:02 AMuli
11/16/2019, 10:03 AMoctylFractal
11/16/2019, 10:05 AMuli
11/16/2019, 10:13 AMoctylFractal
11/16/2019, 10:14 AMuli
11/16/2019, 10:20 AMoctylFractal
11/16/2019, 10:21 AMoctylFractal
11/16/2019, 10:22 AMasync(start = CoroutineStart.LAZY)
could be appropriate. But it requires restructuring regarding the scope.uli
11/16/2019, 10:22 AMMark
11/16/2019, 10:59 AMprivate lateinit var deferredFactory: Deferred<MyFactory>
suspend fun getFactoryOrNull(): MyFactory? = withContext(Dispatchers.Main) {
if (!::deferredFactory.isInitialized) {
val deferred = CompletableDeferred<MyFactory>().apply {
deferredFactory = this
}
try {
createFactory().also {
deferred.complete(it)
}
} catch (e: Exception) {
deferred.completeExceptionally(e)
}
}
try {
deferredFactory.await()
} catch (e: Exception) {
loge("unable to create factory", e)
null
}
}
private suspend fun createFactory() = withContext(Dispatchers.Default) {
// slow stuff
}
uliluckas
11/16/2019, 11:18 AMuli
11/16/2019, 11:29 AMuli
11/16/2019, 11:30 AMMark
11/16/2019, 11:31 AMoctylFractal
11/16/2019, 11:31 AMMark
11/16/2019, 11:32 AMby lazy
uli
11/16/2019, 11:33 AMuli
11/16/2019, 11:33 AMoctylFractal
11/16/2019, 11:33 AMdeferredFactory
is initialized earlier than thatuli
11/16/2019, 11:33 AMMark
11/16/2019, 11:33 AMMark
11/16/2019, 11:34 AMoctylFractal
11/16/2019, 11:34 AMMark
11/16/2019, 11:35 AMprivate val deferredFactory by lazy {
CoroutineScope(SupervisorJob() + Dispatchers.Default).async { MyFactory().also { createFactory() } }
}
uli
11/16/2019, 11:36 AMuli
11/16/2019, 11:37 AMuli
11/16/2019, 11:38 AMMark
11/16/2019, 11:40 AMuli
11/16/2019, 11:40 AMMark
11/16/2019, 11:41 AMuli
11/16/2019, 11:41 AMMark
11/16/2019, 11:42 AMMark
11/16/2019, 11:42 AMMark
11/16/2019, 11:49 AMuliluckas
11/16/2019, 12:05 PMuliluckas
11/16/2019, 12:06 PMuliluckas
11/16/2019, 12:07 PMby lazy
.octylFractal
11/16/2019, 12:08 PMby lazy
uliluckas
11/16/2019, 12:08 PMuliluckas
11/16/2019, 12:08 PMuliluckas
11/16/2019, 12:09 PMMark
11/16/2019, 12:11 PMby lazy
octylFractal
11/16/2019, 12:15 PMuli
11/16/2019, 12:19 PMoctylFractal
11/16/2019, 12:21 PMMark
11/16/2019, 12:21 PMby lazy
vs. GlobalScope.async(start=LAZY)
uli
11/16/2019, 12:21 PMMark
11/16/2019, 12:22 PMawait
, right?octylFractal
11/16/2019, 12:22 PMuli
11/16/2019, 12:22 PMMark
11/16/2019, 12:23 PMoctylFractal
11/16/2019, 12:23 PMoctylFractal
11/16/2019, 12:24 PMby lazy
will have a larger memory footprint because by necessity it adds another layer of object wrapping, Lazy<Deferred<T>>
vs. Deferred<T>
Mark
11/16/2019, 12:25 PMCoroutineScope.async
seems to create quite a few new objects (well, at least two)uli
11/16/2019, 2:12 PMMark
11/17/2019, 5:17 AMtrathschlag
11/18/2019, 8:19 AMtrathschlag
11/18/2019, 8:20 AMinterface SuspendableProvider<T> {
suspend fun get(): T
}
fun <T : Any> suspendableLazy(provider: suspend () -> T) = object : SuspendableProvider<T> {
val mutex = Mutex()
lateinit var computed: T
override suspend fun get() = mutex.withLock {
if (!this::computed.isInitialized) {
computed = provider()
}
computed
}
}
uli
11/18/2019, 8:36 AMimport kotlinx.coroutines.*
interface SuspendableProvider<T> {
suspend fun get(): T
}
fun <T : Any> suspendableLazy(provider: suspend () -> T) = object : SuspendableProvider<T> {
private val computed by lazy { GlobalScope.async { provider() } }
override suspend fun get() = computed.await()
}
Or going right with my solution from above without the SuspendableProvider?trathschlag
11/18/2019, 1:32 PMJob
suspended on SuspendableProvider::get
, the Job
started by async
currently running provider()
will not be cancelled, right? I can solve this by providing a CoroutineScope
but I want to avoid this, as I am joining on the background coroutine immediately anyways. Logically there is no branching behaviour so I don't want that implementation detail to leak into my api.Mark
11/21/2019, 4:46 AMSuspendableProvider<T>
idea. It definitely makes it nicer. For my use case, I definitely don’t want the job to be cancellable so using GlobalScope
makes more sense for me. Also I find using the Mutex
with this::computed.isInitialized
combination, a little hacky. However, I have a personal preference to use start = CoroutineStart.LAZY
instead of by lazy