https://kotlinlang.org logo
Title
m

Mikael Alfredsson

02/04/2021, 1:03 PM
is there a way to perform “awaitAny” on a collection of jobs? i.e wait for first that finishes?
l

louiscad

02/04/2021, 1:40 PM
@Mikael Alfredsson Do you want to cancel the other ones?
m

Mikael Alfredsson

02/04/2021, 1:41 PM
yes. first result “wins” the other ones are not necessary after that.
l

louiscad

02/04/2021, 1:42 PM
Alright, I use this all the time in Android apps, especially UI code (includes infinite suspending loops that I want to run while the race is ongoing)
m

Mikael Alfredsson

02/04/2021, 1:44 PM
looks nice, thanks.
l

louiscad

02/04/2021, 1:46 PM
And here's the permalink to the code that powers these
raceOf
and
race
+
launchRacer
functions.
raceOf
is great for most use cases, but if you have a dynamic number of racers, or late racers,
race
+
launchRacer
is the tool you need: https://github.com/LouisCAD/Splitties/blob/1936c6c7e445c048077d8b4b7bb1c13b99e0ca0[…]coroutines/src/commonMain/kotlin/splitties/coroutines/Racing.kt
@elizarov It's been a bunch of times I see folks that need this use case of racing coroutines and cancelling "losers". Do you think
raceOf
and
race
could make it into the API of kotlinx.coroutines in the current form, or one close to it? EDIT: If the answer is yes, I'll open an issue and a PR for that.
o

okarm

02/04/2021, 1:48 PM
@louiscad Could you elaborate how you use
race
in Android applications?
nevermind, I clicked on the link... 🙄
😄 1
m

Mikael Alfredsson

02/04/2021, 1:51 PM
since awaitAny() would be orthogonal with awaitAll(), I would aim for that first (not supporting dynamic racers). Should be easy to argue for in the API.
l

louiscad

02/04/2021, 1:52 PM
I don't want my API to win, I want the best API to win and stay on the long run, without breaking users of kotlinx.coroutines, and I'm unsure it's the best API.
I don't like
awaitAny()
because its name makes it that it must not cancel the late ones, which is rarely what you want, unless you want to waste resources or have bugs.
m

Mikael Alfredsson

02/04/2021, 1:56 PM
sometimes you don’t want to cancel them. If you only need the result of one, but you still want the “sideeffects” from the others, then cancel is optional.
l

louiscad

02/04/2021, 1:56 PM
Can you give the example of a real-world use case where this would be needed?
m

Mikael Alfredsson

02/04/2021, 1:58 PM
maybe not the normal usecase, but still it would be nice to have that option.
awaitAny
could take a parameter on how to handle the rest. usecase: Send a FCM to all members of the on call support team. We are happy to know that atleast one got it, but the rest should get it if possible
for saving data over multiple nodes, the same can be argued for. We want to know that its saved, but if it can, save to the other ones as well
(im dealing with very flaky transmissions at my current position 🙂 )
l

louiscad

02/04/2021, 2:00 PM
I don't think you'd want
awaitAny()
for that push notification use case. You'd want to watch live how many are getting it out of how many, so you'd get a
Flow
of a ratio, and you could just use
first { it.receivedCount > 1 }
m

Mikael Alfredsson

02/04/2021, 2:01 PM
wouldnt that be completely the same as awaitAny but with flows?
l

louiscad

02/04/2021, 2:01 PM
Then you are asked to evolve to see the progress and the failure rate, and you need to re-architect the whole thing 😅
But yes,
awaitAny()
could do it. The thing is that such an easy way to do it IMHO would lead to people often using the wrong tool, like using
awaitAny()
in places where they should have used
raceOf
or an equivalent that cancels the late ones.
That's why I think it's best to not bring it to kotlinx.coroutines in such a form.
m

Mikael Alfredsson

02/04/2021, 2:04 PM
awaitAny(CANCEL_WAITING)
or
awaitAny(LEAVE_WAITING)
for example
l

louiscad

02/04/2021, 2:04 PM
"waiting" :blob-thinking-fast:
m

Mikael Alfredsson

02/04/2021, 2:05 PM
maybe not the best naming, but I guess you get what I’m aimng for?
l

louiscad

02/04/2021, 2:05 PM
Also, I don't like SCREAMING_CASE, it's distracting and unfair to the rest of the code
Yes, I get it, I always got what you said. I'm also thinking about API naming, knowing it's know to not be easy 😅
One this is that if some code is left uncancelled, it should be explicit/clear from the name of the method.
awaitAny
is explicit, actually, I just fear it's too easy to use it when you don't factor the late runners. Maybe with a required parameter like this?
awaitAny(cancelOthers = true)
WDYT?
m

Mikael Alfredsson

02/04/2021, 2:09 PM
Im trying to think about other use cases, i.e should it be an enum or a flag, but right now I can’t think of any, so a flag that defaults to
cancelOthers
seems reasonable.
someone else probably can find a case for
awaitN(count = 1, cancelOthers = true)
but I cant find that case now 🙂
l

louiscad

02/04/2021, 2:12 PM
I don't think it should have a default value, because "await any" alone doesn't imply cancelling. It's like waiting for someone in a store doesn't imply you close the door for others. Hence, having this default behavior (which is still the most common one I believe, for async programming), would be surprising, with the code kinda lying about what's it doing.
m

Mikael Alfredsson

02/04/2021, 2:14 PM
I like defaults for the most common behaviour, but that might be a personal thing.
anyway, thanks for the
raceOf
code.
:blob-smile-happy: 1
l

louiscad

02/04/2021, 2:17 PM
That's not a personal thing, I also like defaults for the most common behavior, so long it's not suprising or lying when you factor what's actually written.