parallel calls in kotlin without blocking main thread

Viewed 224

i'm learning about kotlin coroutines and scopes and can't understand how to trigger parallel calls by not blocking the main thread. As you can see in the following example 2 calls are triggered in parallel, however the call to .awaitAll() seems to be blocking the main thread as i would expect "I'm blocked message" to be printed before "1.Current thread ..." message. Is this the correct way to implement this type of queries?

current output:
Main thread main
...(After 5 sec) ...
1.Current thread DefaultDispatcher-worker-1
1.Current thread DefaultDispatcher-worker-3
I'm blocked

expected Output:
Main thread main
I'm blocked
...(After 5 sec) ...
1.Current thread DefaultDispatcher-worker-1
1.Current thread DefaultDispatcher-worker-3

class FindItinerariesComposable(private val providers: List<FindItineraries>) : FindItineraries {
    override suspend fun itinerariesForOriginDestination(origin: String, destination: String): List<Itinerary> {
        println("Main thread ${Thread.currentThread().name}")
        return coroutineScope {
            val stream = providers.stream()
                .map {
                    async {
                        it.itinerariesForOriginDestination(origin, destination)
                    }
                }
                .toList()
                .awaitAll()
                .flatten()
            println("I'm blocked")
            stream
        }
    }
}


class FindItinerariesInFlightProvider(private val httpClient: HttpClient) : FindItineraries {

    override suspend fun itinerariesForOriginDestination(origin: String, destination: String): List<Itinerary> {
        withContext(Dispatchers.IO) {
            delay(5000)
            println("1.Current thread  ${Thread.currentThread().name}")
        }
        return listOf(Itinerary("bue", "MIA", "TEST"))
    }
}

fun main() = runBlocking<Unit> {
    val service = FindItinerariesComposable(listOf(FindItinerariesInFlightProvider(HttpClient()),
        FindItinerariesInFlightProvider(HttpClient())))
    launch {
        service.itinerariesForOriginDestination("BUE", "MIA")
    }
}
1 Answers

It seems like you want your cake and eat it, too.

You want to get the result of all those calls on the main thread, before letting the main thread proceed to any further tasks.

You also want to not block the main thread.

If you want the latter, then you must introduce concurrency, where the main thread does other tasks and does not need those results in order to continue.

This may mean a radical change in the design of what you're doing. If you have any code that depends on these results, you'll have to add more code that uses some stand-in or mockup values until the actual results become available. You'll also have to move any code that currently receives the results and acts upon them, into the coroutine.

Here's an outline:

fun main() {
    val service = initializeService()
    val myScope = CoroutineScope()
    myScope.launch {
        val itineraries = service.itinerariesForOriginDestination("BUE", "MIA")
        // do things with itineraries
    }
    // do other things, don't let the main function end
    myScope.cancel() // do this at the very end, when you no longer
                     // need coroutines
}
Related