Software Development

Coroutine Gotchas – Bridging the Gap between Coroutine and Non-Coroutine Worlds | Blog | bol.com


Coroutines are a wonderful way of writing asynchronous, non-blocking code in Kotlin. Think of them as lightweight threads, because that’s exactly what they are. Lightweight threads aim to reduce context switching, a relatively expensive operation. Moreover, you can easily suspend and cancel them anytime. Sounds great, right?

After realizing all the benefits of coroutines, you decided to give it a try. You wrote your first coroutine and called it from a non-suspendible, regular function… only to find out that your code does not compile! You are now searching for a way to call your coroutine, but there are no clear explanations about how to do that. It seems like you are not alone in this quest: This developer got so frustrated that he’s given up on Kotlin altogether!

Does this sound familiar to you? Or are you still looking for the best ways to link coroutines to your non-coroutine code? If so, then this blog post is for you. In this article, we will share the most fundamental coroutine gotcha that all of us stumbled upon during our coroutines journey: How to call coroutines from regular, blocking code?

We’ll show three different ways of bridging the gap between the coroutine and non-coroutine world:

  • GlobalScope (better not)
  • runBlocking (be careful)
  • Suspend all the way (go ahead)

Before we dive into these methods, we’ll introduce you to some concepts that will help you understand the different ways.

Suspending, blocking and non-blocking

Coroutines run on threads and threads run on a CPU . To better understand our examples, it’s helpful to visualize which coroutine runs on which thread and which CPU that thread runs on. So, we’ll share our mental picture with you in the hopes that it will also help you understand the examples better.

As we mentioned before, a thread runs on a CPU. Let’s start by visualizing that relationship. In the following picture, we can see that thread 2 runs on CPU 2, while thread 1 is idle (and so is the first CPU):