I'm trying to do an aync operation of initializing...
# coroutines
j
I'm trying to do an aync operation of initializing a bunch of values. I don't care about the return results. I just want the function to return when I know that all values have been initizlied. I know how to do it this way
Copy code
private fun initializeAll() = runBlocking(<http://Dispatchers.IO|Dispatchers.IO>) {
    map {
        async { it.value }
    }.awaitAll()
}
But is this the best way to do it?
c
Async is really designed to be used for cases where you need the result. If you don’t need the results, just use
launch { }
for each job, the parent scope will run as long as those jobs are active.
Copy code
private fun initializeAll() = runBlocking(<http://Dispatchers.IO|Dispatchers.IO>) {
    jobs.forEach {
        launch { it.initialize() }
    }
}
Also, be careful when using
runBlocking
. 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 threads
j
Thanks for the tip. I'm developing on a Swing platform actually so same rules apply. Basically, I have a subclass of a list, which holds a bunch of lazy values, each of which is an IO operation to disk to get the value. This is part of implementing the
toString()
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.
c
I’d be very wary of using coroutines or
runBlocking
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.
Copy code
// 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)
        }
    }
}
1
e
you definitely don't want to block the main thread in any UI framework, whether it's Android or Swing. do you really need to fetch all the lazy values in
toString()
? that's a poor interface for anything that needs asynchronous computation
1
j
You're right. There are two separate issues here: 1) Blocking the UI thread and 2) ensuring getting all the values in the background utilize multiple background threads asynchronously I believe Casey's proposed example fails the second one. I can easily offload the loading onto a different thread. (I've tested, and I am not blocking the UI thread during loading). But I'd like all the values loaded separately, just to speed up overall load time, since there's quite a few. It looks like corey's
load()
function is doing them one by one.
c
It’s not processing them one-by-one, it’s
launch
-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 it
e
doing work on a background thread with a flag to indicate when it's done is fine. you can do that without
runBlocking
.
j
Sorry Casey, I mean't your most recent example with the load function in the companion object.
c
Yeah, you’re right. I was just putting together a minimal example to show the loading process, the actual loading implementation can be done serially or in parallel. Here’s how it would look loading those values in parallel
Copy code
val aValueJob = async { loadA() }
val bValueJob = async { loadB() }
return LazyValue(aValueJob.await(), bValueJob.await())
Notice we’re using
async { }
and
await()
here instead of
launch { }
because we do care about the return value of each block
j
Makes sense. I think something like this will work for me.
Copy code
// 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
    }
}
e
it's not a good idea to create and discard a
CoroutineScope
like that
1
you can create one that matches your app or screen lifetime, and make sure to save a reference for it
c
You should also use
withContext(Dispatchers.Swing) { }
to move the current coroutine back to the main thread for updating the UI, instead of launching a new coroutine
e
otherwise, what you have there is basically
GlobalScope
, but worse because it's possible GC will collect both it and all coroutines contained within, before they're done
if you already have a scope, just do
EDT_SCOPE.launch(<http://Dispatchers.IO|Dispatchers.IO>) { ... }
j
Okay makes sense. So I have a global val
EDT_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?
Copy code
EDT_SCOPE.launch {
    withContext(<http://Dispatchers.IO|Dispatchers.IO>) { /* Init IO stuff */ }
    // Continue doing swing things
}
e
so
withContext
means you won't
Continue doing swing things
until after
Init IO stuff
is done. if that's what you want, great!
j
That's exactly what I want, since the swing UI stuff relies on the IO to be completed.
e
in theory you could probably build something a bit more reactive by updating values in Swing as they are initialized instead of waiting for all of them, but that's more of a UI design question than coroutines :)
c
It’s probably safer to make the default dispatcher be
<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:
Copy code
// 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 callbacks
e
usually I'd use a
SupervisorScope
for the persistent top-level scope, but it depends on the use case
I don't think it's dangerous to have the default dispatcher be `Main`; the convention is that anything that's blocking should be moving itself onto
IO
c
Depending on the use case, I typically keep the SupervisorScope as part of the CoroutineScope itself, rather than using the
supervisorScope { }
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.html
j
Hm. I didn't think of it until having this discussion, but it would definitely be cool to have the data be displayed as it loads in. I'll definitely be looking into that.
e
yeah,
MainScope()
works fine as long as you have a
Dispatchers.Main
(which I don't remember if
kotlinx-coroutines-swing
sets up by default)
c
Specifically when using
kotlinx-coroutines-swing
, yes
Dispatchers.Main
is an alias for
Dispatchers.Swing
But yup, displaying each value as it comes in is the kind of fun stuff you can do with coroutines! Learning how to use and combine the primitives offered by coroutines allows for some incredibly complex asynchronous workflows that would be all but impossible to do correctly if you had to do everything with traditional callbacks or mess with thread synchronization. So for example:
Copy code
coroutineScope.launch(Dispatchers.IO) {
    launch {
        val aValue = loadA()
        withContext(Dispatchers.Main) {
        	updateUiWithA(aValue)
        }
    }
    launch {
        val bValue = loadB()
        withContext(Dispatchers.Main) {
        	updateUiWithB(bValue)
        }
    }
}
j
I'm using some trickery with property delegates that basically allows me to have a class which is a subclass of
List
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!
u
@Casey Brooks, as mentioned above, the std. pattern for
withContext
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. :
Copy code
suspend fun loadA() = withContext(<http://Dispatchers.IO|Dispatchers.IO>) { // Or better Default?
  // blocking operation to load data into A
}
Otherwise, I like your approach to go with
coroutineScope { }
instead of async.