Let’s explore coroutines with octopuses [Part 1 of ?]

Chris Ward
11 min readMar 27, 2022

Because concurrency is hard, and most people think admitting that means admitting you’re a rubbish developer. Let’s change that record…

Both “Octopi” and “Octopuses” are acceptable by the way. Image source

I’ll be entirely honest. People that fully understood concurrency in Android were always considered God-Level to me — A status I’d never be able to obtain. So I just sat there, nodded and learnt the basics that I’d be asked in interview without ever quite fully understanding. Surviving in the workplace by copying boilerplate from other parts of the codebase, paralysed by the fear that there was more of a scope for me to screw up if I tried experimenting or changing anything than if I didn’t bother at all. And most importantly, I’d never admit my knowledge gap, which removed any chance of it being fixed.

How I finally overcame coroutines, and how I realised I wasn’t alone.

One weekend last year I devoured a book on coroutines. Concurrency was always one of my weaker knowledge-points and I was about to work on a project that required me to know beyond the basics of coroutines and I panicked, because I really didn’t.

So I successfully managed to negotiate with my ADHD-riddled brain to make coroutines a hyperfixation and, at various points when reading and learning, something finally clicked. Well, actually lots of things clicked. Concurrency is hard — you don’t just get one “eureka!” moment.

Bring on the Monday Android standup. Because I finally got all the stuff I’d previously pretended to get, I thought I’d admit what I’d been up to. “Y’know, I never really got coroutines, so I spent the weekend trying to get them, and I think I have.” And then I couldn’t be stopped — I was so excited that I finally understood coroutines, I couldn’t help but explain little tidbits that helped me get to my final eureka moment.

“Wait a minute. This guy doesn’t know the difference between coroutine scopes and coroutine context…” [source]

I expected a few raised eyebrows — an Android developer who writes coroutines but doesn’t understand them? Really? But what I got was admissions from others, including those from whom I considered to be absolute experts on the topic. It was almost as if nobody can ever be a true expert in a topic and there is actually no such thing as somebody who knows all there is to know on a subject. Who knew?!

I was lucky to be on a team where we were open about our weaknesses and helped each other overcome them, but nevertheless I was annoyed by this forced-secrecy effect. Concurrency is very much the perfect example of an area of coding that nobody would dare admit not being proficient in. But it’s also a deeply complex topic — you don’t just know it or don’t know it. Not feeling able to be open about the bits you don’t understand in such a fundamental topic means that so many teaching materials will not cater correctly. It’s also a topic that attracts a lot of surprisingly angry conflicting opinions on discussions that, for most developers, really are inconsequential. But this heat contributes to an environment where people don’t really want to put their hand up and say “yeah I don’t get this, can you explain?”.

I didn’t stop with coroutines. I wanted to really learn concurrency so I could find better ways to teach it to others… (OK, and to get it myself, but ssshhhh!)

I went through books and tutorials on concurrency, painstakingly forcing myself through the dry terminology, the various common pitfalls and problems encountered when sending off different parts of a program to be executed (seemingly) separately. And, importantly, not just how we do things a certain way but why we do things.

And what I realised is that we (mostly) teach concurrency wrong. This isn’t to say those who do tutorials or write books have been dreadful, it’s because we set such a high minimum bar of understanding. And as such, it’s deemed unacceptable for a tutorial to say, “Y’know what, this is a hard and vast topic and instead of throwing everything at you in one go, I’m going to show you how to get started with the most common use cases in a way that you can have a minimal number of screw-ups”. People do not need to know the nuances between the terms “concurrency”, “asynchronous”, “parallelism” etc just to call a basic REST API, yet we tell them anyway. Most developers absolutely do not need to know how Loopers and Handlers work in Android, and nor will they ever need to. We sacrifice ease of learning on the altar of technical-completeness.

In a subject as complex as concurrency, less is more. Rather than overwhelm with a whistlestop tour on every aspect just to MAKE ABSOLUTELY SURE there’s nothing you missed, why not give them the bare minimum, then introduce the complexities and theory in a layered way afterwards, with examples? And analogies that make sense.

This post (and the ones I’ll write after it) is my attempt at a proposal to change how concurrency is taught. I want to make it a talk too, especially for those of us who suffer from the old imposter syndrome or are neurodivergent, but first I want to test the water; see if this gets traction. Any comments/views are more than welcome. Anyway, let’s go…

Level 1. What’s the minimum you, as a coder, need to know about concurrency?

In most apps, there is the active part that the user engages with. Then there’s the stuff that just happens in the background. In code, we generally refer to that active part as the foreground (or UI) and the background part as, well, the background.

The absolute simplest concept for most use-cases, especially in Android, is as follows:

You have the concept of a foreground thread and a background thread*, and long-running functions or those that may take a while to respond (like network calls) should be thrown to the background thread to deal with. Once that work is done, the result of the work (if any) returns to the foreground thread.

(* If you’re screaming “there’s not just one background thread!” you’re the reason I’m writing this piece.)

When writing code for Android in coroutines, what does this mean? Well, it means that this code here calling your Retrofit API from your View Model…

val profile = userApi.getProfile()

becomes…

viewModelScope.launch(Dispatchers.IO) {
val profile = userApi.getProfile()
}

Assuming you’re using and updating observables like LiveData or Flow that your activity or UI subscribes to for updates rather than accessing UI elements directly from the ViewModel (which goes against the whole point of a ViewModel anyway…) this is pretty much all you need to do to be safely doing network stuff in the background.

(Some more experienced developers may be screaming at this code on a design basis and/or the fact that Retrofit uses the IO dispatcher anyway, but I once again refer you to the importance of simplicity to begin with and remind you that this code itself in most cases solves far more problems than it creates.)

OK all done. You can stop reading this article now!

Or not. This is the absolute basic, but it’s enough to get going. This is because coroutines handle the hard stuff that I don’t want to tell you about right now but will tell you if you keep reading to the next levels.

But before deep-diving into coroutines, let’s explore threads in the most basic way possible, because a basic appreciation of the what and why will help you understand so much about what the methods you call in your code are doing on your behalf.

Level 2. Threads explained in the quickest, simplest way possible.

Because threads have been analogied to death, I found that I actually suffered from a huge misunderstanding about how they actually worked (and even what they actually are). But analogies are our best way of understanding a complex topic, so here’s one I feel doesn’t compromise too much on the underlying complexities.

Threads are train tracks.

Chunks of code you want to run are trains.

When your chunk of code (train) is running smoothly, all the other chunks of code (trains behind that train) run smoothly too.

Your UI thread. Isn’t it pretty? Of course it is… it’s the UI. [source]

But when one of your chunks of code wants to do something significantly burdensome it slows down to a stop. Sometimes that chunk of code needs to wait for something else to happen, like your API to respond to a network call, so it stops and doesn’t do anything until your API has responded. If the train has stopped, then all the other trains behind it are stopped too. And we consider that thread (track) to be blocked.

This matters, because on the UI thread, one particular train is especially busy — the one that is responsible for “drawing” the UI. In order to maintain a fluid and smooth experience, that “draw” functionality needs to be called multiple times, possibly for every frame in a second if the user is scrolling or an animation is running. If the thread is blocked, that method won’t be called, and frames will be dropped. The train dealing with listening to your user’s interactions (e.g. presses, drags, etc) won’t be moving either, so your app will be janky or frozen to the user.

Try it out? One way to block a thread is to call Thread.sleep(). In one of your Activity’s event handlers (e.g. a button click) place the following line of code:

Thread.sleep(20000)

Then after triggering that event, try to interact with anything on the screen. You won’t be able to. And at some point, the dreaded “App not responding” (ANR) alert will pop up, offering the user a chance to deal a merciful death to your non-responsive app.

This is why we do not want anything running on the UI thread that is potentially thread-blocking. It impacts the very heart of the user experience.

Threads are a rich and complex topic with all sorts of challenges. But for this level of knowledge, understanding how they work and the primary pitfall of thread-blocking is enough.

Level 3. The Coroutine Octopus

Goodness, it really took this long to introduce the hero?

Have you ever felt overwhelmed by the vast number of libraries available to help you do concurrency in Android and the apparent requirement to know everything about them? Yeah me too.

And it’s ironic really, because these libraries were designed to make your life easier. They all try to do the same thing — take a complex topic and allow you to write code to achieve common things with as little underlying knowledge or boilerplate as possible.

So. Many. Libraries. Can’t. Learn. Them all. [source]

RxJava lets you set off tasks on a background thread. So does AsyncTask. So do coroutines. So does creating a subclass of Thread and calling run(). They’re all different ways of doing the same thing, but one might be better at doing it a certain way, or they do something clever (or a lot of clever things) to make it more efficient. As you’d expect, you’ll find a *lot* of opinions on which one is best and which one is not. For the sake of employability and the direction in which Android development is going, I’d recommend coroutines and maybe still having your toes dipped into RxJava. Here we’ll deal with the latter.

Let’s go back to our trains/tracks analogy. This concept holds no matter what language you’re using — code (trains) will run on a thread (tracks). What differs is how the library manages these. So, instead of two tracks, I want you to imagine a number of tracks, maybe 8 or so for this purpose. And a giant Octopus.

Let’s consider our chunk of code (train) that is about to perform an operation that is potentially thread-blocking (say an API call). This time, however, the train has notified the massive Octopus that it has a blocking-call. When we reach the point at which that operation is called, the Octopus stretches out one of its massive arms and picks the train up, suspending it above the track.

The non-moving train is out of the way of the other trains, so those trains can continue moving. Nothing is blocked. And when the API has responded, the Octopus places it back on the appropriate thread (which may not be the same one as before) for the rest of the code block to be executed.

Actual visualisation of the Android OS and her coroutine octopuses. [source]

Coroutines are marketed as “lightweight threads”, and I sort of get why that is because it invokes the right imagery as to what they do. But really, it’s just yet another library finding another way to manage threads so you don’t have to.

So how do you utilise this magical octopus? Well, any time you have a function that calls something that may block a thread, you mark that function as suspend, so the octopus knows to suspend (yes you will get tired of this analogy; no I am not going to stop).

And you do it in code as such…

suspend fun mySuspendingFunction() {
// contents
}

And that’s it. That’s our signal to our Octopus that this code may do something blocking and, when it does, best lift it off the tracks till the blocking is over.

Summary

This seems like a lot, but you’ve covered almost everything you need to get yourself going on coroutines, including…

  • Knowing that code which does something heavy or relies upon external things like an API should be delegated to the background.
  • How to launch a coroutine to do this.
  • That threads are train tracks, your code is a series of trains, and when your code is waiting for something to complete (e.g. a network call) that train is stopped and everything else on the same track is blocked in the meantime. And that I’m amazing at analogies.
  • That the concept of concurrency is to have multiple threads (tracks) to distribute work. And that libraries such as coroutines and RxJava exist to make this simpler and manage this for you.
  • That coroutines as a concept are essentially a massive Octopus picking trains (code) up and suspending them above the track until they’re ready to move again, ensuring against blockages.
  • That your Android app’s UI thread (train track) needs to be kept unblocked because it is used to constantly redraw the user interface. And that failing to do so will freeze your app’s UI or make it janky.

This is the first of a series of blogs and I will go deeper, but will try not to expose you to anything you don’t *really* need to know yet, so as to not overwhelm you and to minimise the time and effort it takes for you to learn this fascinating but difficult topic.

I really would like some feedback, especially from those who are new to concurrency and how they found this blog. I’d also welcome feedback from seasoned developers — including suggestions, but also whether or not this approach makes sense. It’s a work in progress, but I hope it helped some of you.

Till next time!

--

--

Chris Ward

Berliner. Mobile Engineering Manager and Androider. ADHDer. Posts mainly about tech, politics and mental health.