Joshua Hansen
01/03/2024, 9:32 PMprivate fun initializeAll() = runBlocking(<http://Dispatchers.IO|Dispatchers.IO>) {
map {
async { it.value }
}.awaitAll()
}
But is this the best way to do it?Casey Brooks
01/03/2024, 9:36 PMlaunch { }
for each job, the parent scope will run as long as those jobs are active.
private fun initializeAll() = runBlocking(<http://Dispatchers.IO|Dispatchers.IO>) {
jobs.forEach {
launch { it.initialize() }
}
}
Casey Brooks
01/03/2024, 9:38 PMrunBlocking
. Even though you’re using <http://Dispatchers.IO|Dispatchers.IO>
and the code inside the block will run on background threads, the thread that calls initializeAll()
will still block. So if you called it from Android’s main thread, your app’s UI will freeze even though the tasks are technically running on background threadsJoshua Hansen
01/03/2024, 9:41 PMtoString()
method, which requires that all lazy values be displayed. I inevitably have to block the UI thread for any unaccessed lazy values, so I'm just trying to do that for as little as possible.Casey Brooks
01/03/2024, 9:55 PMrunBlocking
within a toString()
method, that just seems like a recipe for race conditions and poor performance. Probably best to have some function act as a “factory” to load those values and then construct an immutable class containing the loaded values, rather than loading them directly within the class.
// don't do this
class LazyValues {
private var aValue: Int? = null
private var bValue: Int? = null
private suspend fun initializeLazyValuesInBackground() {
aValue = loadAValue()
bValue = loadBValue()
}
public fun toString(): String = runBlocking {
initializeLazyValuesInBackground()
"$aValue, $bValue"
}
}
// do this instead
data class LazyValues(
private var aValue: Int,
private var bValue: Int,
) {
companion object {
public suspend fun load() : LazyValues {
val aValue = loadAValue()
val bValue = loadBValue()
return LazyValues(aValue, bValue)
}
}
}
ephemient
01/03/2024, 10:06 PMtoString()
? that's a poor interface for anything that needs asynchronous computationJoshua Hansen
01/03/2024, 10:19 PMload()
function is doing them one by one.Casey Brooks
01/03/2024, 10:21 PMlaunch
-ing a new task for each item into the thread pool specified by the Dispatcher
. All blocks of launch { }
are going to be running in parallel to each other. But the entire runBlocking { }
block will block as long as there are any active coroutines running within itephemient
01/03/2024, 10:22 PMrunBlocking
.Joshua Hansen
01/03/2024, 10:23 PMCasey Brooks
01/03/2024, 10:26 PMval aValueJob = async { loadA() }
val bValueJob = async { loadB() }
return LazyValue(aValueJob.await(), bValueJob.await())
Casey Brooks
01/03/2024, 10:28 PMasync { }
and await()
here instead of launch { }
because we do care about the return value of each blockJoshua Hansen
01/03/2024, 10:29 PM// From the UI Thread:
CoroutineScope(Dispatchers.Default).launch {
// Do the async init of lazy values here with Casey's example
EDT_SCOPE.launch { // variable I have for the Swing UI thread
// Do UI Stuff here
}
}
ephemient
01/03/2024, 10:29 PMCoroutineScope
like thatephemient
01/03/2024, 10:30 PMCasey Brooks
01/03/2024, 10:30 PMwithContext(Dispatchers.Swing) { }
to move the current coroutine back to the main thread for updating the UI, instead of launching a new coroutineephemient
01/03/2024, 10:30 PMGlobalScope
, but worse because it's possible GC will collect both it and all coroutines contained within, before they're doneephemient
01/03/2024, 10:32 PMEDT_SCOPE.launch(<http://Dispatchers.IO|Dispatchers.IO>) { ... }
Joshua Hansen
01/03/2024, 10:34 PMEDT_SCOPE
(which is a coroutine with the Swing dispatcher) I'm using application-wide. So if I'm not in a scope already, this is better?
EDT_SCOPE.launch {
withContext(<http://Dispatchers.IO|Dispatchers.IO>) { /* Init IO stuff */ }
// Continue doing swing things
}
ephemient
01/03/2024, 10:36 PMwithContext
means you won't Continue doing swing things
until after Init IO stuff
is done. if that's what you want, great!Joshua Hansen
01/03/2024, 10:37 PMephemient
01/03/2024, 10:38 PMCasey Brooks
01/03/2024, 10:39 PM<http://Dispatchers.IO|Dispatchers.IO>
and use explicit withContext
blocks for the comparatively few cases where you need to update the UI. Helps prevent code from blocking the UI (which can be blocked through computation and not necessarily only through IO, so it’s difficult to detect issues). So a more complete example might be:
// coroutineScope should be a coroutineScope bound to the lifecycle of some UI object, such as the Window
coroutineScope.launch(Dispatchers.IO) {
val aValueJob = async { loadA() }
val bValueJob = async { loadB() }
val loadedValues = LazyValue(aValueJob.await(), bValueJob.await())
withContext(Dispatchers.Swing) {
updateUi(loadedValues)
}
}
For getting the coroutineScope
, there might even be facilities within the kotlinx.coroutines lib to provide these scopes, or maybe an example pattern somewhere in the documentation. Failing that, you could manually create the scope and initialize/close it during the appropriate callbacksephemient
01/03/2024, 10:41 PMSupervisorScope
for the persistent top-level scope, but it depends on the use caseephemient
01/03/2024, 10:41 PMIO
Casey Brooks
01/03/2024, 10:45 PMsupervisorScope { }
function or including it in the `launch`: val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
(which is common enough to have its own helper function in the coroutines lib, val coroutineScope = MainScope()
)
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.htmlJoshua Hansen
01/03/2024, 10:46 PMephemient
01/03/2024, 10:48 PMMainScope()
works fine as long as you have a Dispatchers.Main
(which I don't remember if kotlinx-coroutines-swing
sets up by default)Casey Brooks
01/03/2024, 10:50 PMkotlinx-coroutines-swing
, yes Dispatchers.Main
is an alias for Dispatchers.Swing
Casey Brooks
01/03/2024, 10:51 PMcoroutineScope.launch(Dispatchers.IO) {
launch {
val aValue = loadA()
withContext(Dispatchers.Main) {
updateUiWithA(aValue)
}
}
launch {
val bValue = loadB()
withContext(Dispatchers.Main) {
updateUiWithB(bValue)
}
}
}
Joshua Hansen
01/04/2024, 12:38 AMList
where by simply defining the properties in the class, they are members of the list.
I think I'll be able to implement a pretty succinct solution by just iterating over the properties. Thanks for the help!uli
01/05/2024, 9:29 AMwithContext
would be to make loadA
suspend and dispatch to the IO
dispatcher, following the convention that no suspend method should block the calling thread. And then launch on Main
dispatcher. :
suspend fun loadA() = withContext(<http://Dispatchers.IO|Dispatchers.IO>) { // Or better Default?
// blocking operation to load data into A
}
uli
01/05/2024, 9:31 AMcoroutineScope { }
instead of async.