Welcome to my first blog post! I'd like to start it off with a topic that I spend a lot of my professional time working with/thinking about: Kotlin Coroutines! Coroutines aren't a new concept in programming. In short, coroutines allow a function to suspend and resume. The ability to suspend a function has a lot of use cases. In Kotlin, it's most used for performing asynchronous computations. To suspend in Kotlin Coroutines, you'd simply add the
suspend modifier to your function. Just what exactly does adding this
suspend modifier do? How does it work behind the scenes? That's what we'll be exploring in this blog.
First let's see a simple example of a suspending function. The following suspend function just waits for 1 second:
Here we waited for one second but note: we're not blocking whatever thread it's being called on (let's just say main thread for this example). This is all asynchronous. We simply suspended execution of the function, and then 1 second later control of the function is given back. What's even more interesting is that there's no callback involved here.
How are we waiting asynchronously for some amount of time, yet our code is in a synchronous style?. This may seem like magic, but in reality, suspend functions just hide what's actually happening at the Java Bytecode level. We write
suspend in our signature, but the code is actually transformed without us knowing. Let's break it down.
Suspend function signature transformation
Here's what the previous
foo function signature actually looks like after transformation:
What's happening here? A new parameter was added and the function now returns a nullable
Any. The reason we have an
Any? return type is because suspend functions may not actually suspend. They have the
suspend modifier on the signature, but in some cases they may just return without ever suspending or throw an exception (before any suspend). When a suspend function like
foo actually suspends, the actual value of the
Any? will be a special singleton object:
COROUTINE_SUSPENDED. Otherwise it just returns the result.
Now for the
Continuation parameter. It is an interface in the
We'll get more into
CoroutineContext in another blog, but essentially the
Continuation interface just represents a callback (with some state from
CoroutineContext). That's right: coroutines is just writing callbacks for you!
We explained how the signature of a suspend function is transformed, but what does the implementation of suspend functions look like?
Suspend function state machine
I mentioned before that suspend functions are essentially writing callbacks for you. You may think that under the hood there's just this auto generated callback hell happening. But that's not actually the case. Having a bunch of nested callbacks means that you'd have to allocate function objects. And of course that's not very efficient. Instead, the implementation of suspend functions are done with a state machine.
Each suspend function allocates a single state machine object where each suspension point is a state. Also, any local variables in between any suspension points are stored on the state machine. Confusing? Let's look at an example of how this state machine would look like.
First a more complicated suspend function example:
Here's what the function would look like after being transformed by the compiler:
Of course my post-compiler example is not 100% accurate, but it's pretty close. Again as I said, each suspension point is assigned a "state" (the label + any local variables if applicable). The one main object allocated was the state machine. That's what makes suspend function very efficient vs generating a bunch of callbacks!
And that's it! I hope you learned a lot about how suspend functions work underneath the hood! I plan to do multiple blogs that take deep dives into other aspects of Kotlin Coroutines. Next I'll take a look at