Coroutines
Structured concurrency with launch, async, suspend and dispatchers.
Kotlin coroutines are a powerful tool for handling concurrency and asynchronous tasks in a simple and efficient way.
Coroutines are built on the concept of suspension, which lets a function be paused and resumed later without blocking the thread. This is extremely useful for I/O operations, such as network calls, database access or interaction with file systems.
Coroutines live in the kotlinx.coroutines library, which has to be added to the project. On the Kotlin Playground it is
already available.
Starting a coroutine
A coroutine is always born inside a scope. runBlocking creates a scope and blocks the current thread until every
coroutine inside it finishes, which is why it is used in the main function and in tests. Inside it, launch starts a
new coroutine:
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
launch {
delay(1000) // Suspends the coroutine without blocking the thread
println("Coroutine executed.")
}
println("The program keeps running while the coroutine is executing...")
}
runBlocking only finishes after the coroutine started with launch finishes. There is no need to wait with
Thread.sleep.
Structured concurrency
Every coroutine belongs to a scope, and a scope only finishes when all of its coroutines finish. This is called structured concurrency: no coroutine is left running in the background, and a failure in one of them cancels the others in the same scope.
The coroutineScope function creates a scope inside a suspending function. The two coroutines below run at the same
time, and the function only returns when both finish:
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
suspend fun prepareBreakfast() = coroutineScope {
launch {
delay(1000)
println("Coffee is ready.")
}
launch {
delay(500)
println("Bread is ready.")
}
println("Preparing breakfast...")
}
fun main() = runBlocking {
prepareBreakfast()
println("Breakfast is served.")
}
A coroutine can also be cancelled through the Job that launch returns:
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val job = launch {
repeat(10) { step ->
println("Processing step $step...")
delay(300)
}
}
delay(1000)
job.cancel() // Cancels the coroutine at its next suspension
job.join() // Waits for the cancellation to finish
println("Processing cancelled.")
}
Note: Avoid
GlobalScope. It creates coroutines that belong to no scope, that are not cancelled together with the rest of the program and that may keep running in the background. That is why it is marked as@DelicateCoroutinesApi.
Async/await
When the coroutine needs to return a result, use async. It returns a Deferred, and await suspends until the result
is ready. Two calls with async run at the same time:
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
suspend fun checkingBalance(): Double {
delay(1000) // Simulating a network call
return 1500.0
}
suspend fun savingsBalance(): Double {
delay(1000)
return 3500.0
}
fun main() = runBlocking {
val checking = async { checkingBalance() }
val savings = async { savingsBalance() }
val total = checking.await() + savings.await()
println("The total balance is: $%.2f.".format(total)) // Takes about 1 second, not 2
}
The
suspendkeyword marks functions that can be suspended and resumed without blocking the thread. Asuspendfunction can only be called from inside a coroutine or from anothersuspendfunction.
Dispatchers
The dispatcher decides which threads the coroutine runs on. The most common ones are:
Dispatchers.Default: for CPU-heavy work, such as calculations and processing large lists.Dispatchers.IO: for operations that wait on I/O, such as files, network and databases.
The withContext function runs a block on another dispatcher and returns its result, without creating a loose
coroutine:
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
suspend fun sumOfSquares(limit: Int): Long = withContext(Dispatchers.Default) {
(1..limit).sumOf { it.toLong() * it }
}
fun main() = runBlocking {
val result = sumOfSquares(1_000_000)
println("Sum of squares: $result")
}