Writing about Kotlin coroutines is a pretty popular activity nowadays, and there is a good reason for that. The guys at JetBrains created a practical language that conquered hearts of thousands of developers, and introduced a stable (yet still experimental) feature that promises to get rid of the complexity of writing asynchronous code. I'll guide through basic examples of using coroutines, and we will examine what happens under the hood.

Why are solutions like Coroutines necessary?

Handling execution on multiple threads is unavoidable in modern application development. Drawing fancy loaders on the UI while waiting for a network request is just one simple example that you cannot solve without writing asynchronous code. On a mobile OS like Android, the UI thread handles user interactions, such as button clicks and swipe gestures, and also renders the layout itself. These are the basics; let's take a look at an example! After clicking a button, the application downloads JSON data from the network, deserializes it, and updates the layout with the results. (Deserialization and network communication are usually handled by some clever libraries to make the developers' life easier, but for demonstration purposes, let's stick to the example.) How would you code it?

fun fetchUserString(userId: String): String { // request user from network // return user String } fun deserializeUser(userString: String): User { // deserialize // return User } button.setOnClickListener { val userString = fetchUserString("1") val user = deserializeUser(userString) showUserData(user) }

I think this is the most natural (and naive) way of providing a solution for the above-described task. But there are huge problems with it.

Starting a network request on the UI thread is a bad idea (on Android it is not even possible because your app will throw an ugly NetworkOnMainThreadException ) because network requests usually take about at least half a second, during which the UI will be frozen. No fancy loader animations, no scrolling, etc. Deserializing is a high CPU load operation, and can eat huge chunks from frame rendering. Basically, the result is the same as in the previous point.

How can we solve these problems? We need to dispatch our network call and deserialization to a background thread. Sounds easy, but what's the best way to implement it?

Solution 1: Callbacks

Assume we refactored our fetchUserString and deserializeUser functions to do work on a background thread. The program needs to continue the execution with showing the result on the UI after the dispatched work is done. We can achieve this by providing callbacks for the worker functions like this:

button.setOnClickListener { fetchUserString("1") { userString -> deserializeUser(userString) { user -> showUserData(user) } } }

The problem with this solution is that code starts to become less readable as the function calls with callbacks increase. This phenomenon is called callback hell . We definitely want to avoid it.

Solution 2: Reactive approach

The reactive approach provides a better way of writing code because instead of nesting functions into others it lets you compose them:

button.setOnClickListener { fetchUserString("1") .flatMap { userString -> deserializeUser(userString) } .subscribe { user -> showUserData(user) } }

Most of us are very satisfied with the reactive way (myself included), but we have to admit that the code is still far from how we originally wanted to write it - it is a bit more complex for the use case. Is there no other way to express concurrency in code? There is.

Solution 3: Coroutines

This is what the implementation would look like if we used Kotlin coroutines:

button.setOnClickListener { launch(UI){ val userString = fetchUserString("1").await() val user = deserializeUser(userString).await() showUserData(user) } }

It's basically looks the same as the code we first came up with, and behaves as expected (not blocking the UI thread). Let's see how we can write asynchronous code with imperative style like this!

How to use Coroutines?

Coroutines are based on a new type of functions called suspending functions. We can mark methods with a new language keyword called suspend . A function marked with this keyword can suspend a coroutine execution, but is not blocking the thread.

suspend fun fetchUserData(userId: String): String { // return user String }

A suspending function can only be called from coroutines or other suspending functions. Usually we will meet them in forms of lambda parameters on the coroutine library functions, like async :

public fun <T> async( … , … , block: suspend CoroutineScope.() -> T): Deferred<T> { … }

launch{}

The launch function is the simplest tool to use coroutines, if you don't care about return values.

val job = launch { val userString = fetchUserString("1") val user = deserializeUser(userString) log(user.name) }

The wrapped code is dispathced to a background thread, and the function itself returns a Job instance, which can be used in other coroutines to control the execution. Calling the join() method on the Job will supspend the computation of its containing coroutine.

async{}

With using the async function you can achieve the same behaviour as with launch , with one important difference: it can return a value.

val user = async { val userString = fetchUserString("1") val user = deserializeUser(userString) user }.await()

The async function returns a Defered<T> instance, and calling await() on it you can get the actual result of the computation.

After refactoring our code the solution for our problem looks like this:

fun fetchUserString(userId: String) = async { // request user from network // return user String } fun deserializeUser(userString: String) = async { // deserialize // return User } launch(UI) { val userString = fetchUserString("1").await() val user = deserializeUser(userString).await() showUserData(user) }

Now we have an idea of how to get a result value from a coroutine execution but what if we want multiple values? That's what Channel is for - you can read more about them here.

Before looking into the inside of a coroutine, let's check out different implementations of similar asynchronous programming patterns in other languages!

Nothing new under the sun... or is there?

In C# 5.0 Microsoft introduced a very similar feature to Koltin coroutines in form of async functions back in 2012:

public async Task<T> doWork() { // do some work } var result = await doWork()

Their implementation shares the basic principle of how asynchronous code should look like, but there are a few key differences.

In C# async and await are language keywords In C# an async function can only return a Task instance or void

If you take a closer look at the coroutine examples, you can see that in Kotlin, launch{} , async{} , and await() are regular functions (some of them marked with the suspend keyword). This gives a more flexible tool into the hands of the developers in terms of naming and return types.

Threads vs Coroutines

Spawning a thread costs a lot of resources. On a modern system, one thread can easily eat up almost a megabyte of memory. In this context, we can call coroutines (as per the documentation) as "lightweight" threads. Usually, a coroutine is sitting on an actual thread pool, which is used for background execution, and that's why it so effective. It only uses the resources when it needs it. Look at the following code:

val jobs = List(100_000) { launch { delay(1000) print(".") } } jobs.forEach { it.join() }

If you would launch actual threads instead of coroutines the execution would take ages, and maybe wouldn't even finish. An OutOfMemoryException can easily occur with such a huge number of threads, but with coroutines, this is not an issue anymore.

What's inside?

Let's take a look how the signature of the async{} function looks like!

public fun <T> async( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): Deferred<T>

The first parameter is a CoroutineContext , which defines the threads where the execution will take place. It has a default value, which eventually points to a predefined thread pool. But this can be replaced with any other implementation. In the examples above I used launch with the UI CoroutineContext, straight from the Anko library. In that case calling a coroutine with a specific context makes sure that UI-related code (like setting text on labels) is called from the UI thread.

The start parameter is to specify the behavior for when a coroutine should start. By default, when the execution reaches a line with an async{} or a launch{} function, work inside of the coroutine is started. With using the CoroutineStart.LAZY value, a coroutine will only start if the developer explicitly calls await() or join() on the returned Deferred<T> or Job instance.

The magical state machine

The internal coroutine implementation involves the dark arts of some serious compiler magic. With some simplification, here is what's happening:

Every suspending function and lambda has a hidden parameter which is implicitly passed to it when it's invoked. This parameter is a Continuation instance which symbolizes a block of code which should be executed after a coroutine suspension.

Let's see the await function which looks like this:

suspend fun <T> Deferred<T>.await(): T

However, actually it's more like this:

fun <T> Deferred<T>.await(continuation: Continuation<T>): Any?

The result type T of the await is now in the continuation parameter. The Any at the end of the signature is used for the control flow of the coroutine. If it is suspended, the result of the await will be a special COROUTINE_SUSPENDED value. If it's not, it returns the T result of the function.

For each coroutine lambda, a new class is created at compile time. This class is basically a state machine. The compiler analyzes the code and looks for suspending functions - these points will be the states of the state machine (because that's where the execution can be suspended).

As per our example:

launch(UI){ val userString = fetchUserString("1").await() // suspension point #1 val user = deserializeUser(userString).await() // suspension point #2 showUserData(user) }

In this generated class, the execution jumps from one state to another while the thread where the code is running is not blocked. After a suspending function finished, and the result is available, its Continuation parameter can trigger the state machine to jump to the next state.

Exception handling

Exception handling is as usual. Imagine the above-described state machine nested into a huge try/catch block! If something throws an exception, it will be caught and propagated, meaning you don't have to change anything. Business as usual.

launch(UI) { progressBar.visibility = View.VISIBLE try { val userString = fetchUserString("1").await() val user = deserializeUser(userString).await() showUserData(user) } catch (ex: Exception) { log(ex) } finally { progressBar.visibility = View.GONE } }

Cancellation

Cancelling a coroutine is posible, and fairly similar to how you can cancel threads:

val job = launch { // do work } job.cancel()

Calling the cancel() function on a Job or on a Deferred<T> instance will stop the inner computation on a coroutine if the handling of the isActive flag is properly implemented.

val job = launch { while (isActive){ //do work } }

Also, isActive is checked between child coroutine suspension points by the standard library, so you only have to check isActive in your own long-running computations.

Summary

Coroutines are still experimental, meaning the API can change, but the features are already stable - it is here to stay. JetBrains is behind the development with full speed, so I can't wait to see how the future will turn out for this approach to concurrent programming. It's worth experimenting, that's for sure.

Sources

Andrey Breslav - Kotlin Coroutines

MCE 2017: Svetlana Isakova, Coroutines in Kotlin

Kotlinlang.org Coroutines

Github.com Kotlin coroutines