Is there a simple way to execute functions in para...
# getting-started
m
Is there a simple way to execute functions in parallel and wait until all are done? All the examples I find when googling appear to expect a blocking style function instead of already async functions. For instance, I have multiple api endpoints I need to hit, but I don't want to do the model/screen refresh until all 3 have returned. I've simplified the prototype of the functions here a bit for explanatory purposes. I definitely need to call some of these at other times, and don't really want to change their prototype.
Copy code
fun fetchActivities(userName:String,  completion:(List<ActivityEntry>)->Unit) {}

fun fetchMoves(userName:String,  completion:(List<MoveEntry>)->Unit) {}

fun fetchScores(userName:String,  completion:(List<ScoreEntry>)->Unit) {}
2
I believe I'm looking for something similar to pthread's barrier, etc.
i
Unfortunately, I don't know for Kotlin, but in iOS/Swift land that would be called
DispatchGroup
, it that helps with googling 🙂
m
Each of those functions calls Thread.start() btw.
yeah, I know how to do it in swift 😄
😅 1
m
That's why I'm trying to keep the pattern to not accidentally mess up some iOS code I'm porting over semantically
I don't have suspending functions that I'm aware of
o
You can call blocking functions from coroutines. If these are scheduled by the IO dispatcher, blocking won't hurt too much in simple scenarios.
m
These aren't blocking functions though
o
What else are they?
m
Copy code
fun fetchActivities(userName:String,  completion:(List<ActivityEntry>)->Unit) {
   //   This executes after the network request has completed. 
}
They each have a Thread{do network call stuff}.start()
t
You can wrap a completion style call into coroutines if you want to go that route
o
Yep, that's what I was just trying to look up.
m
oi, thank you. That looks exactly how I can wrap these up.
o
And then there's
callbackFlow
for more than one result.
t
as for taking your multiple calls and waiting on all of them, this is how I usually do it
Copy code
listOf(
  async { function1() },
  async { function2() },
  async { function3() }
).awaitAll()
makes it a bit trickier waiting on different return types, so there’s that part to work out. not as simple as I have it above.
m
yeah, if I don't have all three return values, I get an inconsistent picture of the full state. I know there is some hair there with partial failure, etc. But even how to actually wrap the type of function I have was hard to find, "callback" was the word I was not searching for.
t
makes it tricky coming from iOS where they call callbacks completions!
m
lol, yeah, indeed. Amusingly, this far more complex multiple callback thingy is only really required on pull to refresh.
is callbackFlow parallelizable?
t
it’s verbose, but it’s the simplest way I can think of to get it done off the top of my head.
Copy code
val activitiesDeferred = async { activites() }
val movesDeferred = async { moves() }
val scoresDeferred = async { scores() }
val activities = activitiesDeferred.await()
val moves = movesDeferred.await()
val scores = scoresDeferred.await()
callbackflow would be more for returning multiple of the same type
or maybe even a building state that “accumulates” over time
m
Oh that makes sense
t
Copy code
data class FullData(
  val activities: Result<List<ActivityEntry>>?,
  val moves: Result<List<MoveEntry>>?,
  val scores: Result<List<ScoreEntry>>?
)
I would only go that route if the intermediate state were valuable to me
because then as your different api calls return, it starts filling out this state and you can action on partial data
If it’s really all or nothing, I’d go with the sadly inelegant first solution of just declaring 3 async’s and awaiting them all individually.
m
eh, it's only kind of all or nothing
The world is inconsistent if one of the three fails, but they can partially fail, etc.
t
is callbackFlow parallelizable?
It can be but that depends more on the implementation of your Thread situation that happens behind the scenes. A flow can be set on the IO dispatcher which is a big threadpool. But that won’t really matter because you have your own threads inside the implementations
m
I assumed with your 3 asyncs followed by 3 awaits, the asyncs would each launch their own internal threads, and then the 3 awaits would essentially collect it all
Copy code
val activitiesDeferred = async { activites() }
val movesDeferred = async { moves() }
val scoresDeferred = async { scores() }
val activities = activitiesDeferred.await()
val moves = movesDeferred.await()
val scores = scoresDeferred.await()
that guy
t
With coroutines each concurrent coroutine is not necessarily it’s own thread.
Coroutines achieves concurrency without multiple threads
But to answer your question, those three async blocks kick off concurrently and eagerly yes. And then the awaits wait until all of them finish
m
apologies, I didn't mean in general they work that way
I meant "for my particular 3 functions which already go Thread{ doNetworkingStuff() }.start()"
t
Ah yes, so in that situation you have your three threads that kick off, and whatever thread is waiting for them to finish.
The thread that waits for them to finish is determined by what Dispatcher is in the CoroutineScope your
async
block is called from.
m
Tim: I just want to say, I finally got all the error handling in through your triple async solution, it works gloriously well. Thank you. Its so flexible and works so cleanly. I can handle sub-errors in each decode, partial failures, etc.