Lets build a coroutine

Ever since Kotlin introduced coroutines there has been a plethora of posts that showcased their usage in networking or multi threading scenarios. Inevitably many developers when asked what a coroutine is their first answer was “a lightweight thread” or “a way to write clean asynchronous code”. Unfortunately this is not the definition of a coroutine but rather a couple of things that a coroutine can help us with.

Routine (aka Subroutine)

So what is a coroutine? To answer that we must first understand what a routine is.

A routine is nothing more than the common function that we all use every day. Its main characteristic is that its execution must come to a completion before returning to the caller. It does not hold any state and calling it multiple times is like calling it for the first time.

An example:

// a routine:
fun saveUserTasks(userId: Int) {
val user = loadUser(userId)
println("user loaded")
val tasks = loadTasks(user)
println("tasks loaded")
saveTasks(tasks)
}
// when called multiple times:
fun main() {
println("Call #1:")
saveUserTasks(7)
println("Call #2:")
saveUserTasks(7)
println("Call #3:")
saveUserTasks(7)
println("done!")
}
// its like calling it for the first time:
/*
Call #1:
loading a user…
user loaded
loading tasks for provided user…
tasks loaded
saving provided tasks…
Call #2:
loading a user…
user loaded
loading tasks for provided user…
tasks loaded
saving provided tasks…
Call #3:
loading a user…
user loaded
loading tasks for provided user…
tasks loaded
saving provided tasks…
done!
*/
Coroutine

On the other hand a coroutine (a concept that is way older than Kotlin) is a function that can hold its current state allowing us to pause and resume its execution at certain suspension points. This can be of great help when we need to write concurrent code, meaning, when we need to run two tasks at the same time by executing small parts of those tasks one at a time.

For example, if we have task A broken in two parts (A1, A2) and task B broken in two parts as well (B1, B2), we can write code that executes first A1, then B1, then A2 and finally B2.

Building a coroutine

Our goal is to convert the above routine into a coroutine and to do so we need to have a blueprint:

fun saveUserTasks(userId: Int) {
val user = loadUser(userId)
println("user loaded")
// save loaded user
// pause execution
// resume execution
// restore saved user
val tasks = loadTasks(user)
println("tasks loaded")
// save loaded tasks
// pause execution
// resume execution
// restore saved tasks
saveTasks(tasks)
}

So, step #1 is to separate the states into blocks of code and to do that we are going to use the when statement:

fun saveUserTasks(userId: Int, state: Int) {
when (state) {
0 -> {
val user = loadUser(userId)
println("user loaded")
// save loaded user
// pause execution
}
1 -> {
// resume execution
// restore saved user
val tasks = loadTasks(user) // ERROR: unknown user
println("tasks loaded")
// save loaded tasks
// pause execution
}
2 -> {
// resume execution
// restore saved tasks
saveTasks(tasks) // ERROR: unknown tasks
}
}
}

the problem here is that the code does not compile since the states do not communicate and are missing important information.

To fix it we are moving to step #2 where the state parameter will be used as a vessel to pass data from one state to the other:

class State(
var label: Int = 0,
var result: Any? = null
)
fun saveUserTasks(userId: Int, state: State) {
when (state.label) {
0 -> {
val user = loadUser(userId)
println("user loaded")
state.result = user
// pause execution
}
1 -> {
// resume execution
val user = state.result as User
val tasks = loadTasks(user)
println("tasks loaded")
state.result = tasks
// pause execution
}
2 -> {
// resume execution
val tasks = state.result as List<Task>
saveTasks(tasks)
}
}
}

the code now has distinct states that share data but it cannot resume correctly since there is no way to move from one state to the other.

Step #3 addresses that by updating the state’s label allowing the function to resume from where it was paused:

fun saveUserTasks(userId: Int, state: State) {
when (state.label) {
0 -> {
val user = loadUser(userId)
println("user loaded")
state.result = user
state.label = 1
return
}
1 -> {
// resume execution
val user = state.result as User
val tasks = loadTasks(user)
println("tasks loaded")
state.result = tasks
state.label = 2
return
}
2 -> {
// resume execution
val tasks = state.result as List<Task>
saveTasks(tasks)
}
}
}

and that’s it. saveUserTasks is now a coroutine where every call executes a small part of the function allowing us to pause and resume the task:

fun main() {
val state = State()
println("Call #1:")
saveUserTasks(7, state)
println("Call #2:")
saveUserTasks(7, state)
println("Call #3:")
saveUserTasks(7, state)
println("done!")
}
// results in:
/*
Call #1:
loading a user…
user loaded
Call #2:
loading tasks for provided user…
tasks loaded
Call #3:
saving provided tasks…
done!
*/

Concurrency

We did manage to convert a routine to a coroutine but the example did not show the power of using coroutines which is concurrency. Lets change that.

To make things a little bit easier we are going to package a few common functionalities together

abstract class Coroutine {
var isFinished: Boolean = false
private set
protected val state: State by lazy { State() }
protected fun resumeWith(result: Any) {
state.label++
state.result = result
}
protected fun <T> restore(): T {
return state.result as T
}
protected fun finish() {
state.label++
isFinished = true
}
protected class State(
var label: Int = 0,
var result: Any? = null
)
}

which allows us to create these

class SaveUserTasks : Coroutine() {
operator fun invoke(userId: Int) {
saveUserTasks(userId, state)
}
private fun saveUserTasks(userId: Int, state: State) {
when (state.label) {
0 -> {
val user = loadUser(userId)
println("user loaded")
resumeWith(user)
return
}
1 -> {
val user = restore<User>()
val tasks = loadTasks(user)
println("tasks loaded")
resumeWith(tasks)
return
}
2 -> {
val tasks = restore<List<Task>>()
saveTasks(tasks)
finish()
}
}
}
}
class ApplyDownloadedSettings : Coroutine() {
operator fun invoke() {
applyDownloadedSettings(state)
}
private fun applyDownloadedSettings(state: State) {
when (state.label) {
0 -> {
val settings = downloadSettings()
println("settings downloaded")
resumeWith(settings)
return
}
1 -> {
val settings = restore<Settings>()
applySettings(settings)
println("setting applied")
finish()
}
}
}
}

This way we can call the two functions as such:

fun main() {
val saveUserTasks = SaveUserTasks()
val applyDownloadedSettings = ApplyDownloadedSettings()
while (!saveUserTasks.isFinished || !applyDownloadedSettings.isFinished) {
saveUserTasks(7)
applyDownloadedSettings()
}
println("done!")
}
/*
loading a user…
user loaded
downloading settings…
settings downloaded
loading tasks for provided user…
tasks loaded
applying settings…
setting applied
saving provided tasks…
done!
/*

As you can see the two functions are being executed concurrently. First we load the user, then we download the settings, then we load the user’s task and so on and so forth!

Further reading

My goal with this post was to give you a better understanding on what a coroutine is and I did it by using the concept of a finite state machine. The same concept that Kotlin’s implementation of coroutines uses.

For digging in the actual implementation of coroutines I suggest you take a look at a couple of great posts that helped me a lot in grasping the concept:

One thought on “Lets build a coroutine

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s