Table of Contents
What are Kotlin Coroutines?
Kotlin Coroutines came as a boon to me after struggling with asynchronous programming over 4 years of my career as an android developer. Kotlin Coroutines make writing asynchronous and non-blocking code easy to read and understand by eliminating the need to handle traditional callback pattern.
Coroutines are light-weight threads that do not block the main thread while providing the ability to call suspending functions on the main thread. Suspending functions are functions that have the ability to suspend the execution of current coroutine without blocking the current thread. These functions can resume execution at some point. The suspending feature of these functions helps us to write asynchronous code in a synchronous manner. It is also worthwhile to note that suspending functions are not asynchronous themselves.
A suspending function can be written by adding a prefix to a function with the keyword “suspend” like so:
As we saw, suspending functions are the core concepts of Kotlin Coroutines. But how do we call these functions? Suspending functions can only be called from another suspending function or from a coroutine. The next question that immediately pops up in our mind is: how to start a coroutine? The answer is Coroutine Builders.
What are Coroutine Builders?
As the name suggests, coroutine builders are a way of creating coroutines. Since they are not suspending themselves, they can be called from non-suspending code or any other piece of code. They act as a link between the suspending and non-suspending parts of our code.
In the following sections, we will be discussing the following three Kotlin coroutine builders:
As the name suggests, runBlocking is a coroutine builder that blocks the current thread until all tasks of the coroutine it creates, finish. So, why do we need runBlocking when we are clearly focusing to avoid blocking the main thread?
We typically runBlocking to run tests on suspending functions. While running tests, we want to make sure not to finish the test while we are doing heavy work in test suspend functions.
We will see how easy it is to use them in the following section.
launch is essentially a Kotlin coroutine builder that is “fire and forget”. This means that launch creates a new coroutine that won’t return any result to the caller. It also allows to start a coroutine in the background.You can create a new coroutine from launch as follows:
You can create a new coroutine from launch as follows:
In the above code, we call the suspending function,
doSomething() and after 3 seconds of delay we print the string: “Did something that is 3 seconds long”.
Note: “delay” in Kotlin, is a suspend function which delays a coroutine without blocking the current thread. It then resumes the coroutine after the specified time (In our case, 3 seconds or 3000 milliseconds).
We now move on to the best part of Kotlin Coroutines builders – async.
async is a coroutine builder which returns some value to the caller. async can be used to perform an asynchronous task which returns a value. This value in Kotlin terms is a Deferred<T> value.
We can simply use async to create a coroutine doing some heavy work like so:
The output for the above code would be, “Hello, this is a bad example for using async!!!”.
doSomething() function doesn’t return anything, what is its use?
I have created the above example precisely to point out this error. It is highly discouraged to use async when you have nothing to return. Using launch should suffice in this case, like so:
await() is a suspending function that is called upon the async builder to fetch the value of the Deferred object that is returned. The coroutine started by async will be suspended until the result is ready. When the result is ready, it is returned and the coroutine resumes.
Here’s an example of how to use await to fetch result from a coroutine:
The output for the above code would be:
Gone to calculate sum of a & b
Carry on with some other task…
Sum of a & b is: 30
Here’s how the above code works:
async will return a Deferred value as the result of
calculateSums() by calling the suspending function
await() on it.
await() will fetch us the result that calculateSum()/ async returns.
calculateSum() is executing, the following happens:
async builder will suspend the coroutine (used for calculating sum).
The execution of other tasks (print statement) continues.
calculateSum() returns a value, the suspended coroutine will resume and print the calculated result.
Now, coming to the best part about using Kotlin Coroutines with async. We can use async coroutine builder to call multiple pieces of asynchronous code in parallel. We can then combine the results from both to display a combined result. This leads to lower latency and in extension, a superior user experience.
Let’s jump directly to an example to get the gist and later see how it works.
In the example above, let us assume the functions
isSecondCriteriaMatch() perform long running tasks that return Boolean values. These Boolean results are used to decide what message to show to the user.
If we go by the traditional callback pattern, we would follow the steps below:
isFirstCriteriaMatch() and save the result in a global variable
isSecondCriteriaMatch() and save the result in another global variable
3. Check results from both functions and display messages accordingly.
However, with async and await, we save considerable amount of time. We make asynchronous calls to both functions in parallel and wait for each function to return a value before showing the result.
Thus, async suspends the coroutine while
isSecondCriteriaMatch() are executing and as soon as both return a value, the coroutine is resumed to print an appropriate message.
GlobalScope in coroutines and why to avoid it
If you haven’t noticed already, I have used GlobalScope in all my examples above to help ease the understanding of the internals of coroutine builders. Typically, use of GlobalScope is discouraged. Do you know why?
Quoting definition of Global Scope from Kotlin’s documentation– “Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely.”
GlobalScope creates global coroutines and these coroutines are not children of some specific scope. Hence, it is the developer’s responsibility to keep track of all such global coroutines and destroy them when their job is done. This burden of manually maintaining the lifetime of coroutines can lead to extra effort on the developer’s part. Also, if not handled properly, it can very well lead to memory leaks. Hence, use of GlobalScope should be avoided. However, as we saw, all coroutines must be created within some coroutine scope. Then, what is the recommended way?
private val mainScope = MainScope()
Every coroutine builder is an extension of coroutine scopes and inherits their context. Thus, every scoping function (coroutineScopes created) provides its own scope to its children and internally waits for each child coroutine to complete before marking itself finished. Thus, memory leaks are avoided internally.
Here is a wonderful explanation on why to avoid GlobalScope by Roman Elizarov (Team Lead for Kotlin):
Also refer the official Kotlin documentation for detailed understanding of coroutine scopes and how they work at this link:
Kotlin coroutines make it super easy to develop Android apps if you know how to use them in the right way. By writing asynchronous code with suspend functions and coroutine builders, one can avoid the confusing callback pattern.
For example, consider the case when we have to call multiple APIs and do something after assessing the response from all of the APIs. Instead of a complex chain of calling APIs, we can have a simplified coroutines version. This will lead to a cleaner and self-explanatory code.
Here a couple more articles to read on Kotlin coroutines!
1. https://medium.com/mindorks/multiple-concurrent-asynchronous-calls-using-kotlin-coroutines-async-await-and-suspendcoroutine-26371fa49781 – For understanding how to avoid callback mechanism and still be able to achieve concurrency with asynchronous tasks