Coroutine basics
- What is coroutine?
A coroutine is an instance of suspendable computation.
- What does it do?
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.
- What benefits does it have?
Lightweight, Built-in cancellation support, Fewer memory leak, etc…
Key functions
runBlocking
runBlocking
is also a coroutine builder that bridges the non-coroutine world of a regular fun main() and the code with coroutines inside of runBlocking { ... }
curly braces. This is highlighted in an IDE by this: CoroutineScope
hint right after the runBlocking
opening curly brace.
fun main() = runBlocking { // this: CoroutineScope
launch { doWorld() }
println("Hello")
}
// this is your first suspending function
suspend fun doWorld() {
delay(1000L)
println("World!")
}
lauch
launch is a coroutine builder. It launches a new coroutine concurrently with the rest of the code, which continues to work independently.
fun main() = runBlocking { // this: CoroutineScope
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
}
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.
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")
async
async
starts a new coroutine and returns a Deferred
object. Deferred
represents a concept known by other names such as Future
or Promise
. It stores a computation, but it defers the moment you get the final result; it promises the result sometime in the future.
The main difference between async and launch is that launch is used to start a computation that isn’t expected to return a specific result. launch returns a Job that represents the coroutine. It is possible to wait until it completes by calling Job.join()
here is the official example of async
import kotlinx.coroutines.*
fun main() = runBlocking {
val deferred: Deferred<Int> = async {
loadData()
}
println("waiting...")
println(deferred.await())
}
suspend fun loadData(): Int {
println("loading...")
delay(1000L)
println("loaded!")
return 42
}
More concepts
Scope builder
Besides the built-in coroutine builders, you can declare your own scope using the coroutineScope builder which creates a coroutine scope and does not complete until all launched children complete.
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
Global Scope
you can also start a new coroutine from the global scope using GlobalScope.async
or GlobalScope.launch
. This will create a top-level “independent” coroutine.
Cancellation
Coroutine cancellation is cooperative. A coroutine code has to cooperate to be cancellable. All the suspending functions in kotlinx.coroutines are cancellable. They check for cancellation of coroutine and throw CancellationException when cancelled.
However, if a coroutine is working in a computation and does not check for cancellation, then it cannot be cancelled.
val job = launch(Dispatchers.Default) {
repeat(5) { i ->
try {
// print a message twice a second
println("job: I'm sleeping $i ...")
delay(500)
} catch (e: Exception) {
// log the exception
println(e)
}
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
Coroutine context
The coroutine context is a set of various elements. The main elements are the Job
and its dispatcher
. The coroutine dispatcher can confine coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined.
launch { // context of the parent, main runBlocking coroutine
println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher
println("Default : I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
The Dispatchers.Unconfined coroutine dispatcher starts a coroutine in the caller thread, but only until the first suspension point. After suspension it resumes the coroutine in the thread that is fully determined by the suspending function that was invoked.
Shared mutable state and concurrency
This topic is really important in the currency world so it’s less prone to bugs if you understand it very well. Just gonna share the official link here for you to explore. https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html
Comparison to async/await in JavaScript
Here is a classic example in which you have a async
function and writing code like synchrounous by using await
. Wrting asynchrous code becomes really easy since ES7.
async function task() {
const response = await fetch("example.com");
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// ...
}
It is worth noting that async
and await
are both keyword in ES7 and above. That means this is language level feature and sugar syntax for developers. While in kotlin, async
is a method and there is really only one keyword suspend
in kotlin.
In Kotlin, you can not call suspend
function out of scope in main thread so you need a bridge function like runblocking/lauch/async
, which is very much like await
in JavaScript.
But things get to change since ECMA2022, one of the biggest feature is Top level await, meaning you can use the await
keyword on its own (outside of an async
function) at the top level of a module.