Kotlin Coroutines [Part-1]
kotlinx.coroutines
is a rich library for coroutines developed by JetBrains. It contains a number of high-level coroutine-enabled primitives that this guide covers, including launch
, async
, coroutine context
and dispatchers
.
This is a guide on the core features of kotlinx.coroutines
a series of examples, divided up into different topics.
What is a Coroutine?
A coroutine is an instance of suspendable computation. It is conceptually similar to a thread, in the sense that it takes a block of code to run that works concurrently with the rest of the code. However, a coroutine is not bound to any particular thread.
launch { // launch a new coroutine and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello") // main coroutine continues while a previous one is delayed
}
Output:
Hello
World!
Each coroutine has it’s own CoroutineScope and CoroutineContext.
CoroutineContext can be from parent to child, the child inherits the properties of parent coroutine.launch is a coroutine builder.
delay is a special suspending function.runBlocking is also a coroutine builder that bridges the non-coroutine world of a regular
fun main()
and the code with coroutines inside ofrunBlocking { ... }
curly braces. This is highlighted in an IDE bythis: CoroutineScope
hint right after therunBlocking
opening curly brace.
If you remove or forgetrunBlocking
in this code, you'll get an error on the launch call, sincelaunch
is declared only in the CoroutineScope.
Unresolved reference: launch
Extract function refactoring
Let’s extract the block of code inside launch { ... }
into a separate function. When you perform "Extract function" refactoring on this code using Android Studio, the official Android development IDE, you get a new function with the suspend
modifier.
Suspending functions can be used inside coroutines just like regular functions, but their additional feature is that they can, in turn, use other suspending functions (like delay
in this example) to suspend execution of a coroutine.
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch { doWorld() }
println("Hello")
}
// this is your first suspending function
suspend fun doWorld() {
delay(1000L)
println("World!")
}
Output:
Hello
World!
Scope builder
The runBlocking and coroutineScope builders may look similar because they both wait for their body and all its children to complete. The main difference is that the runBlocking method blocks the current thread for waiting, while coroutineScope just suspends, releasing the underlying thread for other usages. Because of that difference, runBlocking is a regular function and coroutineScope is a suspending function.
fun main() = runBlocking {
doWorld()
}
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
Output:
Hello
World!
Scope builder and concurrency
The coroutineScope builder can be used inside any suspending function to perform multiple concurrent operations. Let’s launch two concurrent coroutines inside a doWorld
suspending function:
// Sequentially executes doWorld followed by "Done"
fun main() = runBlocking {
doWorld()
println("Done")
}
// Concurrently executes both sections
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
launch {
delay(2000L)
println("World 2")
}
launch {
delay(1000L)
println("World 1")
}
println("Hello")
}
Both pieces of code inside launch { ... }
blocks execute concurrently,
with World 1
printed first, after a second from start, and World 2
printed next, after two seconds from start. A coroutineScope in doWorld
completes only after both are complete, so doWorld
returns and allows Done
string to be printed only after that:
Output:
Hello
World 1
World 2
Done
An explicit job
A launch coroutine builder returns a Job object that is a handle to the launched coroutine and can be used to explicitly wait for its completion. For example, you can wait for the completion of the child coroutine and then print “Done” string:
val job = launch { // launch a new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
println("Hello")
job.join() // wait until child coroutine completes
println("Done")
Output:
Hello
World!
Done
Reference: