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

rrva

01/29/2019, 7:22 AM
Using coroutine syntax to make orchestration of API calls easier to read/write is something I found useful. I could easily see it be so useful that it should go into kotlinx-coroutines... Here I have blocking calls, but I still find coroutine syntax elegant and simple to reason about. Like this:
Copy code
private fun <A, B> Iterable<A>.parallelMap(context: CoroutineContext, f: suspend (A) -> B): List<B> = runBlocking {
        map { async(context) { f(it) } }.map { it.await() }
    }

val listOfIds.parallelMap(fooApiPool) { fooApiClient.fetchStuff(it)}
(found at: https://jivimberg.io/blog/2018/05/04/parallel-map-in-kotlin/) But what about orchestrating calls to several sources (clients calls could be made suspendable if this is important), where we for example have three source API:s which we would like to be called in parallel, and then the values are awaited and collected? (I'm trying to get rid of RxJava for some use-cases where I found it to be a burden to maintain/debug). What are good patterns here for expressing the execution flow?
g

gildor

01/29/2019, 7:36 AM
Blocking parallelMap based on coroutines looks really strange. I wouldn’t use such implementation
r

rrva

01/29/2019, 7:37 AM
it's useful for stuff which does not run in a coroutine, just using coroutines to execute some blocking stuff in parallel
g

gildor

01/29/2019, 7:38 AM
It is not. It’s just bad style that encurage implicit conversion of non-blocking operations to blocking
r

rrva

01/29/2019, 7:39 AM
fooApiClient.fetchStuff() in this case is a blocking operation already
g

gildor

01/29/2019, 7:42 AM
This implementation is really bad, for example, you cannot cancel this operation, it’s really easy to shoot in your own foot if someone pass wrong context that has dispatcher that shouldn’t be blocked
if
fooApiClient.fetchStuff
is blocking, you shouldn’t create special functions to run it, but instead, at least wrap it to coroutine, so it will be non-blocking at least
r

rrva

01/29/2019, 7:43 AM
The way I use it is with a dedicated dispatcher
g

gildor

01/29/2019, 7:43 AM
If it work for you, fine But you said
I meant parallelMap should go into the standard coroutine library
and I pointed,. that this is bad implementation that shouldn’t be used if you don’t understand all the pitfalls of it. And it’s of course shouldn’t be in kotlinx.coroutines
There is feature request about standard working pool pattern in kotlinx.coroutines, which related, but proposed solution doesn’t involve (of course)
runBlocking
or any blocking solutions
r

rrva

01/29/2019, 7:45 AM
ok, I can see the problems with making such a function generally available.. I just thought it was an elegant way to write something a bit similar to parallelStream in syntax
Why not just use parallelStream? also parallelStream uses under the hood ForkJoinPool which has much more complicated logic and optimised for parallel blocking operations
so I would say not “elegant way to write parallelStream” but “naive way to write parallelStream”
Also, you say that
fooApiClient.fetchStuff
is blocking, but why it’s blocking? This example looks as some network request, which of course shouldn’t be blocking and much more optimal way would be to use non-blocking version of network request
r

rrva

01/29/2019, 7:57 AM
Yes, agreed this could be optimized for making coroutines a more natural fit. I could have used parallelStream for this example I guess. Onto my next question, what about good patterns to orchestrate calls to multiple sources (let's say these are all HTTP calls, which I could rewrite to be non-blocking). Any examples of this? Lets say we have the dependency that we want to fetch data in parallell from clientA, clientB, clientC, and when these three are done, we invoke clientD. In this case I would need all clients to be non-blocking and use
awaitAll
?
g

gildor

01/29/2019, 7:58 AM
I would need all clients to be non-blocking and use
awaitAll
?
It’s not something what you must to do, but it’s definitely would be good in terms of performance and coroutines ideology
Your data sources also can be blocking (or some of them), just wrap those calls to
suspend fun call(): withContext(IO) { blockingCall() }
, so it allows you to use them with non-blocking code, but this approacch is also not perfect, you don’t have proper cancellation support, but it’s already closer to usual coroutine usage
for http requests I don’t see reasons to use blocking API with coroutines
8 Views