https://kotlinlang.org logo
Title
z

zokipirlo

02/23/2022, 9:52 AM
Is there any difference between this two implementations? Which solution is preferred? 1.
supervisorScope{
    list.map {
        async { doSomeApiCall(it) }
    }.awaitAll()
}
2.
supervisorScope {
    list.map {
        async { doSomeApiCall(it) }
    }
}.awaitAll()
e

ephemient

02/23/2022, 9:55 AM
the
.awaitAll()
isn't needed either way because a scope does not end until all its children are completed
👍 3
j

Joffrey

02/23/2022, 9:56 AM
@ephemient
awaitAll
still converts
Deferred
into their inner value, and I guess the code is incomplete here but I'm assuming the OP would want to get those values. However I agree that it doesn't await anything anymore, they will all be immediately converted. If the result of
awaitAll()
is really ignored like in the exemple, it's indeed pointless.
It can behave differently if there is other code inside the
supervisorScope
after
map
. In the first case, the extra code would be executed after the map, while in the second case it will run concurrently with the map. But there is no such code in your example
I would personally prefer the first option, because it's clearer that you await on list returned by the
map
. It's less visible when applied to
supervisorScope
. Also I like to see
supervisorScope
as blocks, so calling a function on it seems weird.
2
s

Sam

02/23/2022, 10:06 AM
👍 stylistically, I prefer option 1, because as @ephemient said, the jobs will all be complete by the time
supervisorScope
returns anyway. If you were to await them outside the scope, you're really just unwrapping them after they're already finished. Awaiting them inside the scope is more representative of what will actually happen.
z

zokipirlo

02/23/2022, 10:21 AM
I thought
awaitAll
is needed because it starts
async
block and without
awaitAll
nothing will execute. All examples I saw was with
async
block, that's why I did it like that. I guess I could change
async
to
launch
because result from suspend function is ignored in my case?
j

Joffrey

02/23/2022, 10:23 AM
If you don't need results, then you can indeed simplify by removing
awaitAll()
. Also,
launch
is better because it expresses that you don't care about the result
🍺 1
👍 2
s

Sam

02/23/2022, 10:35 AM
The default behaviour of
async
is to start eagerly, just the same as
launch
(you can change that by passing a
CouroutineStart
argument). But there are some circumstances where that still might not start immediately, for example when using
runBlocking
, so maybe that accounts for the examples that you saw.
1
b

bezrukov

02/23/2022, 8:57 PM
One more difference that wasn't mentioned yet - immediate awaitAll will fail as soon as any of async calls fails. The second variant will wait for the completion of all async calls, even if some of them fail (because of supervisor scope)
👍 3
e

ephemient

02/23/2022, 9:10 PM
just a bit more nuance: the first variant will fail when the first (in list order) async call fails, and the bubbled exception will cancel any remaining async calls still in flight. because it is contained in
supervisorScope
, other async calls may fail without notice. which is somewhat similar but not the same as the behavior of
coroutineScope {
    list.forEach {
        launch { doSomeApiCall(it) }
    }
}
which will cancel any remaining calls when the first (in execution order) launch fails.
b

bezrukov

02/23/2022, 9:45 PM
In list order
It is not true, awaitAll fails once the first in execution order fails, from the doc:
This function is not equivalent to deferreds.map { it.await() } which fails only when it sequentially gets to wait for the failing deferred, while this awaitAll fails immediately as soon as any of the deferreds fail.
e

ephemient

02/23/2022, 9:51 PM
TIL. thanks
z

zokipirlo

02/23/2022, 10:10 PM
So the safest solution is with launch because I don't want others be cancelled if any exception happens?
e

ephemient

02/23/2022, 10:19 PM
if that's what you want, then yes, launch and don't explicitly
.join()
the jobs
g

gildor

02/24/2022, 5:44 AM
So the safest solution is with launch because I don’t want others be cancelled if any exception happens?
If this is your explicit requirement, I wouldn’t bother with supervisorScope at all and instead wrap doSomeApiCall to try/catch and required error handling