https://kotlinlang.org logo
#coroutines
Title
# coroutines
r

rtsketo

06/10/2021, 9:54 AM
Usually when I want to launch a banch of coroutines but also want to wait for all them to finish, I do something like this:
Copy code
val jobs = arrayListOf<Job>()
aListOfStuff.forEach {
  jobs.add(launch { something(it) })
}

jobs.joinAll()
println("All jobs completed!")
Recently I discovered I can do something similar with
supervisorScope
, like so:
Copy code
supervisorScope {
  aListOfStuff.forEach {
    launch { something(it) }
  }
}

println("All jobs completed!")
Does it completely mimics the behavior I intented the first part to have though? Are there be any mihaps I should be aware of?
e

Erik

06/10/2021, 11:51 AM
I think your
supervisorScope {}
call just launches these coroutines and returns after the last launch. So you don't have the guarantee that when you
println("All jobs completed!")
that all jobs are in fact completed.
In a regular coroutine scope (without a supervisor job), when one child fails, that failure is propagated to all children (cancelling them) and the coroutine scope itself becomes cancelled. On the other hand, a supervisor scope (or job) is one that is meant to let child coroutines (e.g. the ones that you
launch
) run independently. When a child fails, the failure isn't propagated to other children, and the supervisor isn't cancelled. This is something different than joining all jobs.
So I would say that your original approach does what you want, but your supervisor approach does not.
Meta: you can even
list.map { launch {} }.joinAll()
for a one-liner that doesn't require you to instantiate a jobs list yourself.
👍🏼 1
😮 1
r

rtsketo

06/10/2021, 12:19 PM
But
joinAll()
still doesn't garentee that all jobs are completed, isn't that correct? Even if one job fails, `All jobs completed!`will be printed.
1
e

Erik

06/10/2021, 12:26 PM
What you likely see is that when one child job fails, then the rest becomes cancelled. And then they're all complete
So maybe you want to launch the children in a supervisor and then join them all (combine your first and second approach)
With a supervisor, a failing child will not cancel other children. And
joinAll
will suspend until all children complete: either when they cancel (normally or exceptionally) or successfully complete.
Maybe you want something like
supervisorScope { list.map { launch {} } }.joinAll()
d

Dominaezzz

06/10/2021, 1:17 PM
I don't think you need to join all in supervisor scope. Otherwise it's no better than global scope.
z

Zach Klippenstein (he/him) [MOD]

06/10/2021, 2:47 PM
I think your
supervisorScope {}
call just launches these coroutines and returns after the last launch. So you don't have the guarantee that when you
println("All jobs completed!")
that all jobs are in fact completed. Pretty sure
supervisorScope
won’t return until all jobs are completed, just like
coroutineScope
- that’s the whole point of those functions, to delineate a structured concurrency scope with a lexical scope.
e

Erik

06/10/2021, 3:28 PM
I see that the
coroutineScope
docs specifically mention:
This function returns as soon as the given block and all its children coroutines are completed.
But the
supervisorScope
docs fail to mention that. It just has a signature that implies no such behaviour. And the docs don't mention that it behaves similar to
coroutineScope
(the difference being the supervisor job).
That's where I likely went wrong
z

Zach Klippenstein (he/him) [MOD]

06/10/2021, 4:17 PM
Docs could probably use some work. Implementations have the same shape as well FYI
e

Erik

06/10/2021, 4:28 PM
Opening a PR as we speak
🙏🏻 1
🙌🏻 1
🙏🏼 1
🙏 for teaching me something today!
2 Views