Kotlin coroutines real life example

I love Kotlin and it’s concept of coroutines attracts me. But first I didn’t understand two simple things about using coroutines on backend.

Coroutines are useless without non-blocking API

People usually think: “OK, I put this Spring RestTemplate request within async coroutine and everything becomes non-blocking and efficient!”

Alas, coroutines documentation is a bit misleading. As soon as RestTemplate calls are blocking all your coroutines will also block. You won’t gain anything from calling blocking methods be it HTTP or database requests.

Let’s mentally trace the call – a thread is started, another coroutine is started inside the thread, coroutine makes HTTP call and blocks the thread until the response comes. There’s no magic here. If you have only one thread the whole application halts.

There’s a simple rule: non-blocking + blocking = blocking

To gain from coroutines you should only use non-blocking API: Spring Reactive Web Client or Ktor Http Client for HTTP requests, R2DBC for database queries.

Coroutines only reduce number of threads

— OK, I use Ktor HTTP Client with coroutines. Does everything run very fast now?
— No, it’s not faster than if you start a separate thread for every call. Just use multi-threading.

But if you are making a really large number of simultaneous requests the coroutines are here to serve. Remember that threads are expensive. They cost memory as well as setup and context switching time. Hundred of threads is already a large number.

If your application makes a lot of calls and most of the time waits for the response you should definitely stick to coroutines.

A real life example

Consider you are parsing data from 1000 different sites. You need to make a call to every site wait for the response and then do something with aggregated data.

With multi-threading you will start 200 threads in a pool each of them eating memory, quickly sending a request and then sleeping. As some threads are finished they return to thread pool, make another call and sleep again.

With coroutines you could make all 1000 simultaneous calls in one thread!

fun parseAll(urls: List<String>) = runBlocking {
    val client = HttpClient { }

    val results = urls
            .map { url ->
                async {
                    client.get<String>(url)
                }
            }
            .awaitAll()

    // Do something with results
}

All calls are started in a single thread one by one. After a request is sent thread switches to another coroutine. When all requests are sent thread sleeps until the results start coming. After all responses are received thread continues with results collected.

Is that all about coroutines?

Coroutines are more than just a tool to reduce number of threads. They suggest a rather clean and error-prone concept for dealing with asynchronous operations. But this is mostly useful on frontend where number of threads is limited to a few or even one. At server-side you could use coroutines when making many calls to external services which are waiting most of the time.

Additional benefit is that when you skip multi-threading you avoid all the concurrency problems.

Published by Andrey Minogin

Full-stack Web/Java developer

Leave a comment

Your email address will not be published. Required fields are marked *