concurrency in kotlin
We tried out some concurrency coding in Kotlin this week in my team. We are building an android app with a backend (also in kotlin) that reaches out to several other micro services to get information. A new endpoint we built was quite slow (upwards 15 seconds to respond or so) in some cases so we decided to do something about it. As is best practice we hadn’t really built specifically for performance before but waited for contact with reality before optimizing for speed. Kotlin has a really nice library called kotlinx.coroutines that can be used for this purpose. Consideering how clumsy this is to do with Kotlins bigger brother Java it was a nice surprise to discover the possibilities here. From the languages I have worked with I hold Go as having best built-in support for concurrency but this is reallt on the same level. Lets look at some examples.
Calling two things at the same time
A common case for parallelism is when you need to get data from several independent services. The naive approach is - of course - to call one first and then the other. This is typically also the easiest code to read. Say you have a function to call some slow animals:
fun callSomeSlowAnimals(): CombinedAnimalResponse {
val tortoiseAnswer = callTortoise()
val slothAnswer = callSlot()
return CombinedAnimalResponse(tortoiseAnswer, slotheAnswer)
}
fun callTortoise(): String = "Hello"
fun callSloth(): String = "Hi there"
// and the rest you can figure out....
Pretty straightforward. Now lets do these calls in parallell instead:
fun callSomeSlowAnimals(): CombinedAnimalResponse =
runBlocking {
val tortoiseAnswer = async { callTortoise() }
val slothAnswer = async { callSloth() }
CombinedAnimalResponse(tortoiseAnswer.await(), slotheAnswer.await())
}
}
suspend fun callTortoise(): String = "Hello"
suspend fun callSloth(): String = "Hi there"
// and the rest you can figure out....
As you can see we have introduced 4 new building blocks here to make this happen. First there is the runBlocking
call that sets up a context where parallelism can happen. Calls to async
can only happen inside one of these.
The we have async that accepts a block that you want to call asynchronously. It returns an object of the type Defferred
. This is much like a future or promise in some other language.
Then to actually wait for the call to finish we need the await()
call to the Deferred
instances. The first one of these that is encountered will wait for a response. But other calls to async happens in the background so we will have two things going on.
Finally the functions we call are marked with the keyword suspend
. This is needed to say that these are possible to call in an asynchronous manner. They are thread safe.
Calling many things at the same time
So this is all good but perhaps you want to call to all present tortoises at the same time. But you don’t really know how many these are. In comes channels that can be used to kinda save asynchronous answers while moving on to new calls. Say you have a function that calls a bunch of tortoises one after the other:
fun callACoupleOfTortoises(): List<String> =
repeat(5) { callTortoise() }
fun callTortoise(): String = "Hello"
Lets do this with channels and coroutines instead:
fun callACoupleOfTortoises(): List<String> {
val answers = mutableListOf<String>()
runBlocking {
val ch = Channel<String>(5)
repeat(5) { launch { ch.send( callTortoise() ) } }
ch.close()
repeat(5) { answers.add( ch.receive() ) }
}
return answers
}
suspend fun callTortoise(): String = "Hello"
So this is a bit messier but still quite clean. We have the runBlocking
thing to tell kotlin that we are gonna to async stuff. Then we create a channel for strings. We now it’s gonna be of 5 capacity at this point but it can be dynamic as well. We use launch
to do things asynchronous. This is not a promise but rather a job. Then we send the response from our tortoise to the channel. The stuff inside the launch happens in parallell. When we have sent all the things we close the channel and move on to receieving results from the channel. And the function needs to be suspended. Pretty straightforward.
written by fredrik at 2024-12-13
More content on code
More content on kotlin
More content on programming