An Overview on Kotlin Coroutines

Luciano Almeida
8 min readJul 20, 2019

--

In today’s post, we are going to explore what Kotlin Coroutines are and how they work. This is going to be a high-level overview, so if you want more detail, check out the reference section videos and articles.

Definition

A coroutine is a function that has one or more suspension points. Different from a normal function, which is called, then initializes the local state, executes and terminates. It will have a start, and at each suspension point the function saves the local state (value of variables declared in the scope of that function) and gives the control back to the caller, remaining in that suspended state until it is resumed. This happens for all suspension points until the function ends, i.e. there are no more suspension points.

Let’s see an example:

yeilding a sequence example

The hi () function above is a coroutine with two yield points where when we start the loop, the coroutine yields the value of the first yield “Hi” and enters the suspended state, where the caller will continue execution (in the example println the value) and the next iteration, the coroutine will be resumed and execute the instructions until the next point of suspension yield “:)” again enters the suspended state, where the caller will continue execution (in the example println in value) and in the next iteration, since there is no longer a suspension point, the coroutine ends as a normal function.

Just to clarify what exactly are the sequence and yield in the code sample above we have the sequence that is the coroutine builder (that we’ll talk about later) created for yielding the values of the sequence inside the coroutine scope (SequenceScope). The yeild is a built-in suspend function that as the documentation states: “Yields a value to the Iterator being built and suspends until the next value is requested.” We are going to talk about both coroutine builders and scope later in the article.

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence-scope/yield.html

How all this works?

Coroutines are a generic mechanism used by various programming languages to implement various features, such as async / await and generators. Some examples of other languages that implement the mechanisms of coroutines are Python, C ++ and Go.
There are some implementation details of coroutines that are base pillars of how the mechanism will work and should be considered by programming languages taking into account the approaches that fit better the goals for coroutines in that specific language.

So, let’s take a brief overview of the way these pillars and some other details:

1. Ceding control to the caller

It’s how the coroutine will enter into suspension state and give the control back to the caller.
Two strategies are usually used to implement this in a language:
Context Switching: Where the compiler generates a normal function, but at the suspend points, the yield value is saved in a place, where the caller can find it, doing that by changing the context in runtime.
Coroutine Splitting: Compiler generates an initial function (Ramp function). And a (Shared Resumption) that will handle all suspension points when the coroutine resumes. Or instead of a shared resumption, it can create several (Per yield Resumption) functions one to handle each suspension point.

2. Storing the Local State

When a coroutine enters in the suspension state, it should store its local context, i.e. local variables declared within the function and the last point of suspension from where it should be summarized when it resumes.
There are some approaches to that:
The first one is called Stackful Coroutine: When the coroutine is created, along with it is also created a new stack. The frames, local variables and everything this coroutine executes will be handled by this new stack. That is deallocated when the coroutine ends. The problem of that is that it will have all the performance costs of allocating and maintaining a new stack.
The second is called Stack Cohabitation: where the coroutine uses the same stack normally (pushing the frames, allocating local variables in the stack …), but when it enters the suspended state, it does not execute the pop on the stack only move the pointer to the frames of the function that called it, that is, the frames and local variables (which are usually allocated on the stack), remain in the same place and when it resumes it back to that point in the stack.
And finally, we have the Side Allocation: Where only allocates space in the heap to store the local state of the coroutine. It has the cost of heap allocation which can be a problem depending on the goal.

3. Yield

It’s how the yield value is passed to the function that called the coroutine.
Return: When the control is ceded using Coroutine Spliting, the yield value is returned directly from the ramp / resumption functions.
Store on Fixed Location: Saves the values in a fixed location where it can be accessed later by the caller.

To sums it up, those are mechanisms that are used to implement coroutines in general. All of that is explained in detail by John McCall in the talk 2018 LLVM Developers’ Meeting: J. McCall “Coroutine Representations and ABIs in LLVM”.[1]

But… how all this works in Kotlin?

A coroutine in Kotlin is defined by the reserved word suspend and it can only be called by another function that is also a suspend function or inside a CoroutineBuilder that we are going to talk about below.

Defining a suspend function

The above function is a coroutine, which calls two functions that are also coroutines, that is, suspend functions.
And the function below is this same function implemented using callbacks.

The same logic implemented using a callback

But how the Kotlin compiler would generate that function under the hood?

The mechanism implemented by the compiler uses a Continuation object and a state machine that will handle the suspension and resume points of the coroutine.
But first, what is a Continuation? Well, a continuation is just an interface that provides an abstraction for the implicit coroutine callbacks.

Definition:

The definition for the Kotlin Continuation object.

As we can see above, it has a method resumeWith that will resume the coroutine successfully or with an error.
We will see an example of how we could use the continuation to transform a code implemented with another mechanism e.g. Retrofit Call handler, in a code that uses the coroutines to make network requests.
Going back to our example, we see below pseudo codes of how the Kotlin compiler will generate our getUser function.

The labels for the suspension points

First the compiler will generate an implicit continuation parameter that will be passed to the calling function. And it will also generate a label for each suspension point.

Pseudo code of how the compiler handles suspension

Also, a state machine is generated that will handle the coroutine at each suspension point and will be resumed. Each label will be treated and the state updated.

Example: When the coroutine is resumed in label 0 (zero) it will execute the call to token API, which is a child coroutine, passing the state machine object as continuation to it. It is not shown in the pseudo code above, but in addition to the calls, the status machine label is updated to 1 (one) and the object returned in apiToken is stored in the context of the state machine.
This resume and update state keeps going until the coroutine finishes.
Note that this generated function that handles the various sleep states has a mechanism similar to the one we saw in the beginning called Shared Resumption.

There are two great talks [3] and [4] by Roman Elizarov that go into detail in all this if you are curious.

So… now that we have a high-level notion of how those things work. Let’s see a practical example of how we can use it on a day-to-day project.

But, before we look into the sample application, let’s understand two concepts:

Coroutine Builders

Are functions that create coroutines within a given CoroutineScope.
* async / await: Async is a builder that is used to perform some processing and return a result. Starts a coroutine where the result is represented by an instance of Deferred.
await must be used to wait for the result of the coroutine.
* launch / join: The launch is used to fire a coroutine. Join may wait for the execution of the coroutine. What means that we can wait for the result of the Job using join().
*Important: An exception thrown inside the launch is just logging into a JVM backend application, but crash into the android.

CoroutineScope

It defines a scope for new coroutines, where each coroutine inherits the context of that scope. Each coroutine builder (such as launch, async, etc) and every scope function (such as coroutineScope, withContext, etc.) provides its scope in the callback that executes. By convention, they all expect all coroutines within the block to be completed before they complete themselves.
You can use either the GlobalScope (not recommended) which is a global scope of coroutines or implement CoroutineScope in one of your classes. The recommendation is that you implement CoroutineScope on always in classes with a well-defined life cycle e.g. in an Android Activity.

Launch with a coroutine scope

Note in the example above that launch function, that is the coroutine builder has within the block it has CoroutineScope.
Which then changed using withContext to the UI Thread to modify the view.

Note: When we use launch without specifying a Dispatcher, it will use the default of the coroutineContext. That is declared here.

The sample project

There is a sample project here that we developed with the goal of demonstrate in practice how to use coroutines and the diference between that and Rx.

We are not going to show step-by-step on the demo, but instead we are going to highlight how you can:

  • Use a coroutine builder to create a context where suspend functions can be called. Here
  • Implement Coroutine Scope in your own custom class. Here
  • Change between contexts inside the coroutine scope. Here
  • How to turn a Retrofit Call into a Coroutine. Here and also here. Obs: There are some libraries like this one that alredy to that.
  • The difference between coroutine call and a Rx call. Here

Conclusion

The purpose of this article was to talk a little bit about what Kotlin’s coroutines mechanism are, how they work and how we can use them as an alternative to asynchronous programming for use in our day to day projects.

That’s all for this article \o/

If you got some comment or question, please let me know. Your feedback is really important so we can improve this and the future posts and it will be great to receive it :))

Thanks for reading :)

Thank’s section

This article is in short a brief overview of a talk that we did internally on our company a while ago. So a huge thank’s to Guilherme Prado for pairing up on doing the talk and the sample project.

References

  1. 2018 LLVM Developers’ Meeting: J. McCall “Coroutine Representations and ABIs in LLVM”. by John McCall
  2. The one and only StackOverflow
  3. KotlinConf 2017 — Deep Dive into Coroutines on JVM by Roman Elizarov
  4. KotlinConf 2017 — Introduction to Coroutines by Roman Elizarov
  5. Kotlin Coroutines and Retrofit
  6. Kotlinx Coroutines
  7. Kotlin Coroutines Reference

--

--

Luciano Almeida

Aspiring Compiler Engineer, Swift and OpenSource enthusiast