Asynchronous programming with Kotlin and Coroutines — Lesson 2: Suspension Points

Filip Babic
Background Thread
Published in
6 min readJul 24, 2018

I advise you to check out the first part of the series, which explains what Routines are:

Proceeding with our series on programming execution, routines and asynchronous programming, we’ll be talking about suspension points in code, and how they define which turn your program will take.

They’re both abstract terms which are hard to grasp, so, to continue the good spirit, I’ll provide you with an everyday analogy to simplify it for you.

So let’s try to elaborate these questions in an understandable way. :]

Suspension points

Last time we were quite optimistic about our program’s execution. We looked at code as if it’s always executed in sequence, and as if each line of code followed the happy path, being executed without any hiccups. More often than not, this is not the case. Programs are human-made, and as such, they tend to malfunction, or they exit unintentionally.

Moreover, sometimes your program has to stop at a certain point, take a break, do something else quickly, and then continue. In these cases, when programming synchronously, you don’t have a choice, you just have to wait and hope for the best.

In asynchronous programming, things are a little bit different, you can suspend some code, basically starting a routine and skipping over to the next line, continuing the progress. After a while, the suspended code is finished and you can use its result, continuing the execution of the parent routine, or simply ignore it if you don’t need the result.

A good analogy to explain this is the art of making cookies. When you’re baking sweets, unless you prepare virtually every ingredient beforehand, so that you only have to turn the oven on, you’re probably running around hectically, preparing the dough, mixing the cream or grating the chocolate.

In modern words you’re multitasking. But what you’re really doing is creating a few small subroutines, each of which can be suspended, allowing you to do something else for a while, then ultimately returning to the previous routine, doing so until everything’s done.

What can also happen, when your cookies are in the oven, is that someone knocks on your door. If baking sweet goodies was synchronous, you’d have to turn everything off and pause it until you deal with the person. The synchronous art of making cookies would look like this in code:

fun bakeCookies() {
val oven = Oven()
val ingredients = prepareIngredients()

val cookies = makeCookies(ingredients)

getTheDoor()
val bakedCookies = oven.bake(cookies) eatCookies(bakedCookies)
}

You wouldn’t be able to start baking them, without answering the door first. And you wouldn’t be able to do any other work, like clean up your kitchen, before the cookies were baked.

But since baking cookies is async, you can leave the baking and quickly jump to the door, attaching some means of notifying you when the cookies are done. The worst thing that can happen is your sweets get burned and you get no result. :[

Async approach would look like this:

fun bakeCookies() {
val oven = Oven()
val ingredients = prepareIngredients()

val cookies = makeCookies(ingredients)

oven.bake(cookies) { bakedCookies -> eatCookies(bakedCookies) }
//the bake function is suspended, it's result is passed on
//through a callback
getTheDoor()
}

You start baking the cookies, but not before you set the baking timer, which will notify you of the cookies being done. This can be done various ways. Here we’ve used a commonly known construct— a callback.

Small interruptions, like your doorbell and the food burning up are all points in routines that pause or otherwise prematurely end the execution. They’re also called suspension points. Because when your doorbell rings, you cannot proceed with baking, you have to suspend your current work and tackle the doorbell.

Suspensions can be both a good and a bad thing. A bad suspension would be if you’ve started making the dough, and you realise your oven is broken or you don’t have all the ingredients. Your work is immediately suspended with an exception, something you didn’t anticipate, and your progress so far is lost. A negative suspension, due to lack of ingredients for cookies would something like this:

fun bakeCookies() {
val oven = Oven()
val ingredients = prepareIngredients()

if(ingredients.isEmpty()) {
throw IllegalArgumentException("Missing ingredients")
}
...
}

But if your cookies are in the oven, you don’t really need to do anything around them. You wait until they are done — the result is finished. The worst thing that could happen is that your cookies burn up, and the delicious food is lost, once again resulting in an exception.

If, however, the oven rings, and your food is well prepared, you instantly get notified of the result, and you can proceed with your execution — eating sweets! This is a good suspension, as you’re relieved of any work from the moment you put your food in the oven to the point of it being nicely baked.

So to put it in technical terms, suspension points are points in code which either end your program early (mostly bad paths in programs), or which start some work on the side, in another routine which is suspended, ultimately notifying you of the end result, and allowing you to continue where you left off.

And while this sounds easy enough, how is it that the program knows of its current state, where it’s been suspended at, and when and where to return back? This is all done via another construct which we’ll talk about next.

Note: Suspension functions are quite a complex topic on it’s own, so we’ll talk about them in the context of coroutines in next articles.

Routine lifecycle

Another thing to realise here is that, when a parent routine is finished, its children don’t have to necessarily finish as well. They can still work in the background. Or, if the parent hasn’t finished, it can use their result anytime in the future, if that happens at all. It’s one of the strongest benefits of code suspension. But it could also produce memory leaks if we’re not cautious.

This concept has also been materialised in many structures, such as futures, generators, coroutines and promises. We’ll talk more about how the system moves around the code, how it leverages priorities and availability of threads in execution, what coroutines are and how computing is done with coroutines later on in the series.

Conclusion

To be able to structure an optimised program and to cover the pitfalls of asynchronous programming, it’s very important to understand how the computer works with your code. By learning what suspension points are and how they define program flows, you gain valuable insight on program execution, which you can use to your advantage later on.

Sometimes, not just in programming, it’s better to think like a machine and to organise your tasks and everyday routines like a computer would. It might save you time and stop your cookies from burning up!

--

--

Filip Babic
Background Thread

Android developer. Praise Kotlin :] Keen and enthusiastic learner and mentor, passionate about teaching and helping others. GDE @ Android, Osijek, Croatia.