I am currently in the process of porting an Android app and I am therefore learning Kotlin at the same time, since this is now the recommended language for Android development. I have to say, I like this new language a lot : it reminds me of the conciseness and expressiveness of Python but with a robust typing system and bits of functional programming mixed in. Lots of great ideas (a lot of them inspired from a wide range of other languages) made their way into the language, such as :
- baked-in null safety that allow you to get rid of those pesky NullPointerException’s before you even write them
- extension functions make your code cleaner and more idiomatic without endless class derivations
- wide reliance on lambdas and higher-order functions, with simple and clean function types, greatly improving code clarity again
- type inference everywhere possible to reduce boilerplate code
- syntactic sugar making possible to define entire DSLs directly into your code
Example of a one-liner possible in Kotlin :
// If there are 10 users or less that are in their 20s, print their names println(users.filter { it.age in 20..30 } .takeIf { it.size <= 10 } ?.map { it.name } ?.joinToString() ?: "Too many users")
All of this, in addition to recent Android features such as LiveData and ViewBinding (think Observer pattern), makes the new implementation way shorter, clearer and more robust than the old Java one.
However, there is one other major feature offered by Kotlin that I had trouble wrapping my mind around : coroutines. Shortly speaking, coroutines are an interesting way to help you write safe, efficient and easy-to-read concurrent or asynchronous code. While powerful, this feature can be difficult to grasp at first when you are only used to the classic multi-threading coding techniques.
The official documentation goes into great lengths to explain what coroutines do and where they can prove useful, but is short on details about how they do it. I understand that some (most?) people don’t need more to get started, but for my down-to-earth mind, the examples seemed a bit “magical” at times and that, for me, made things paradoxically harder to understand.
This humble post is therefore an attempt at explaining coroutines from another, bottom-up point of view, hopefully helping similar-minded people to me to comprehend what is going on behind the curtain of coroutines, why they are useful, and what are their limitations. In a way, it aims at being the guide I would have wanted to have when I started learning them. It does not try, however, to be exhaustive : I will assume that you have already read (or intend to read) the official documentation (at least the first chapters).
A side note : as I said, I only recently started learning Kotlin, and I am nowhere near what you would call an expert. Everything explained below is to be taken with a grain of salt and is mainly destined to offer another perspective on the subject. The official documentation is obviously the definitive source of information, and if you spot a mistake, please report it in the comments below. Thanks!
- A quick reminder on processes, threads, and CPUs
- Sequential execution
- Multi-process and scheduler
- Threads
- Cores
- Different approaches to multitasking
- Concurrent execution and its share of problems
- Single-thread is not great either
- An interesting alternative
- An internal, cooperative software scheduler
- Coroutines are not concurrent
- Suspending functions
- Dispatchers and cooperative scheduling
- Asynchronous execution
- Cancellable suspending functions
- Coroutine scopes and structured concurrency
- Coroutine context
- Starting a coroutine
- Examples
- Example 1 : my first coroutine
- Example 2 : non-concurrency
- Example 3 : yielding
- Example 4 : multi-threading
- Example 5 : context switching
- Example 6 : sub-scope
Let’s get started !
Very, very nice article! Many thanks!
It would be very nice to learn more about exception handling in coroutines!
Great article. I love that your examples also cover blocking calls like Thread.sleep and how specifying the dispatcher helps in this case. This would have helped me a lot when I first started working with coroutines!
This is by far one of the BEST introductions to coroutines I’ve read. I’ve shared this with my ream at work.
Thank you for putting this together. The diagrams are amazing! I love how you build intuition, I’ve been wanted to put something like this together for some time but I don’t think I could have done such a good job.
Small nitpick:
In this sentence:
>> “it would only require to change launch() for another coroutine builder : withContext()”
Calling withContext() a coroutine *builder* can be misleading, as doesn’t actually create a new coroutine, it just changes the scheduler (or context should I say). https://pl.kotl.in/T-XZv31xL
>> “This function suspends the current coroutine while the code inside its block is executed.”
Again, there’s only one coroutine 🙂 just in a different context. And the coroutine doesn’t suspend get suspended when calling `withContext` (might suspend later tho)
Hi Fernando, thanks for the comment and the suggestions !
I checked again the doc of withContext and indeed, I had misunderstood its behavior. I think I was mislead by the part that says “suspends until it completes”, which at first I thought meant that the current coroutine was suspended and implicitly that a new one was created. I fixed that paragraph and I hope it is now correct, but please let me know if you see some mistake.
Dude you’re amazing, your prose is completely intuitive and easy to follow. NOW I can read that damned documentation and make sense of it.
Thank you slicia.io for the explanation