
Coroutines in Kotlin
Kotlin offers a better way to perform asynchronous tasks, called coroutines. Coroutines are a new and fluent way of writing asynchronous and non-blocking code in Kotlin. They are much lighter than threads and are easy to manage. Both coroutines and threads are multitasking, but they differ in that threads are managed by the kernel and coroutines are managed by the code itself, thereby giving programmatic control.
Coroutines were introduced in Kotlin 1.1 as an experimental feature. There are two types of coroutines:
- Stackful
- Stackless
A stackful coroutine is a normal function that can be suspended during execution. It will be suspended with its entire stack, including its local variables, and the parameters passed from the invoking function. The stackful coroutine waits for the scheduler to resume its execution, similar to what a thread does. The scheduler can resume either the same coroutine or another suspended coroutine. A stackless coroutine, on the other hand, doesn't have its own stack, so it doesn't have to map on the native thread and doesn't require context switching on the processor.
Coroutines in Kotlin are based on the idea of suspending functions that can stop execution when they are invoked and resume execution once the task is complete. This utilizes the CPU better than blocking the execution in its entirety.
Functions can be suspended using the suspend keyword, and may only be invoked inside other suspended functions or inside a coroutine. Coroutines can run concurrently, similar to threads in Java, and wait for one another, communicating during execution. They are light compared to threads, meaning we can create thousands of them without worrying about the slow performance of an application.
As mentioned earlier, coroutines have been introduced as an experimental feature in Kotlin and are provided as a library. We need to include this dependency in the Maven or Gradle, or with the classpath in your project:
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>0.22.5</version>
</dependency>
Let's create a Maven project to demonstrate coroutines:
- Create a Maven project using the kotlin-archetype-jvm archetype.
- Specify the groupid and artifactid.
- This will create a project with the following structure:
As coroutines have experimental status in Kotlin 1.1, the compiler reports a warning every time coroutines are used by default.
To avoid the compiler yelling at us whenever coroutines are used, we have to add kotlin-maven-plugin to the coroutines flag enabled in pom.xml:
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xcoroutines=enable</arg>
</args>
</configuration>
</plugin>
Note that the kotlinx-coroutines-core JAR is published at http://jcenter.bintray.com. We have to add it to the repository section in our pom.xml file:
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
The Kotlin library provides different ways to create the coroutines. The most simple way of creating this is the launch {} function.
Now, let's write our first coroutine:
fun main(args: Array<String>) {
println("Inside main")
launch {
println("Coroutine in execution")
}
Thread.sleep(2000)
println("main completed")
}
The program prints the output as follows:
This program starts a coroutine and prints the message. The main thread must wait until the coroutine finishes the execution, otherwise the program will end prematurely.
The launch function starts a new coroutine. By default, it runs in a shared pool of the thread that is in execution. Threads exist in a program and each thread can run multiple coroutines. This means that the coroutines are much lighter.
There is a construct in Kotlin that can be used to suspend the coroutine—the delay() function. This function takes time as input which can be of long/int type:
fun main(args: Array<String>){
println("Inside main")
launch{
println("delay in 2 seconds")
delay(2000L)
println("Coroutine in execution")
}
Thread.sleep(2000)
println("main completed")
}
The output is as follows:

When a coroutine is in a waiting or suspended state, the thread is returned back to the pool. When the waiting/block state is over, the coroutine resumes its execution on a thread that is available in the pool.
Let's create functions that can be called from a coroutine:
private suspend fun game1(): String {
delay(1000)
return "game1"
}
private suspend fun game2(): String {
delay(2000)
return "game2"
}
fun main(args: Array<String>) {
println("Inside main")
launch(CommonPool) {
val one = game1()
val two = game2()
println("Game ----- " + one)
println("Game ----- " + two)
}
Thread.sleep(4000)
}
The output is as follows:

So far, we have discussed how to create and run coroutines. Note that all the coroutines that we have created so far execute in a serial fashion. In the preceding example, we created two coroutines—game1() and game2(). These were executed serially in the main thread. Let's now create coroutines that run concurrently in the main thread.
We can use async() to create and execute two coroutines concurrently, as follows:
private suspend fun game1(): String {
delay(1000)
return "game1"
}
private suspend fun game2(): String {
delay(2000)
return "game2"
}
fun main(args: Array<String>) {
println("Inside main")
launch(CommonPool) {
val one = async(CommonPool){
game1()
}
val two = async(CommonPool){
game2()
}
println("Game ----- " + one.await()+": "+two.await())
}
Thread.sleep(4000)
}
This gives us the following output:
The asynchronous function returns an instance of the Deferred<T>, type, which has an await() function that returns the result of the coroutine.
Now let's look at one more example, where we can really use the power of parallel execution:
fun main(args: Array<String>) {
println("Inside main")
launch(CommonPool) {
val deferred = (1..1_000_000)
.map { n -> async { n }
}
var sum = deferred.sumBy { it.await() }
println("sum ----- " + sum)
}
Thread.sleep(4000)
}
The output is as follows:
When we are performing long-running operations, we need to run these as background tasks.
In this section, we have learned different ways to create and manage coroutines in Kotlin in order to execute a number of operations in the background, both serially and concurrently.