This post has been republished via RSS; it originally appeared at: Microsoft Mobile Engineering - Medium.
Taking a look at how suspend functions actually work underneath the hood. What does Kotlin generate?
On Outlook Mobile Android, we’re big fans of Kotlin Coroutines. We started using them last year and it’s been a great success so far (although not without some pains). 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 kotlinx.coroutines package:
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 are 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.