Asynchronous Programming in Kotlin
Asynchronous Programming in Kotlin
Asynchronous
Programming
in Kotlin
● Kotlin coroutines
● Inside CoroutineScope
● Channels
● More
Parallel programming
blocking
call
Blocked
Parallel programming
● In reality, programs (threads) spend a lot of time waiting for data to be fetched from
disk, network, etc.
● The number of threads that can be launched is limited by the underlying operating
system (each takes some number of MBs).
● Threads aren’t cheap, as they require context switches which are costly.
● Threads aren’t always available. Some platforms, such as JavaScript, do not even
support them.
● Working with threads is hard. Bugs in threads (which are extremely difficult to debug),
race conditions, and deadlocks are common problems we suffer from in multi-threaded
programming.
Thread
An example
What we want
submitPost
Thread
processPost processPost
An example
What we get
Thread
processPost processPost
An example
With callbacks, the idea is to pass one function as a parameter to another function and have
this one invoked once the process has completed.
● There are different APIs, which vary across libraries, frameworks, and platforms.
This looks and feels sequential, allowing you to focus on the logic of your code.
marks suspension points in IntelliJ IDEA.
The history of coroutines
History and definition
● Melvin Conway coined the term “coroutine” in 1958 for his assembly program.
● Coroutines were first introduced as a language feature in Simula’67 with the detach
and resume commands.
● Coroutines calling each other (and passing data back and forth) can form the
machinery for cooperative multitasking.
Coroutines came to Kotlin in version 1.1, and they became stable in version 1.3.
Into:
fun submitPost(token: Token, item: Item, cont: Continuation<Post>) {...}
Where:
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}
processPost
token submitPost
preparePost post
SUSPENDED
Practice
Now we can finally post items without blocking the execution thread!
fun nonBlockingItemPosting(...) {
...
postItem(item)
}
Practice
Now we can finally post items without blocking the execution thread!
fun nonBlockingItemPosting(...) {
...
postItem(item)
}
The suspending function postItem should be called only from a coroutine or another
suspending function.
suspend functions can be called from other suspend functions or within CoroutineScope.
jobs.forEach { it.start() }
● A child’s failure immediately cancels its parent along with all its other children. This behavior can be
customized using SupervisorJob.
Job States
cancel/fail
finish
Cancelling Cancelled
Job states
● A view of a dispatcher with the guarantee that no more than parallelism coroutines are
executed at the same time can be created via:
fun dispatch(
block: Runnable,
taskContext: TaskContext = NonBlockingContext,
tailDispatch: Boolean = false
){
...
}
}
A peek under the hood
● Contexts can be added together. In this case, the rightmost value for a Key is taken as
the resulting context.
● Since each Element implements CoroutineContext, this looks like a sum of elements.
Context switching
Main
IO
Default
time
How is this actually better than
threads?
Main post
IO
Default
time
How is this actually better than
threads?
Main post
withContext(Dispatchers.IO)
IO fetch
Default
time
How is this actually better than
threads?
Main post
IO fetch blocked
Default
time
How is this actually better than
threads?
Main post
Default
time
How is this actually better than
threads?
Main post
withContext(Dispatchers.Defalt)
Default process
time
How is this actually better than
threads?
Main post show
Default process
time
How is this actually better than
threads?
Main post Not blocked show
Default process
time
How is this actually better than
threads?
Main post like sub dwnld post calc post upd
time
Coroutines - fibers - threads
WRONG!
The default behavior is sequential, you have to ask for concurrency.
Coroutines - fibers - threads
It is not guaranteed that the coroutine is going to be resumed on the same thread, so be very careful about calling
suspending function while holding any monitor.
Job
launch SupervisorJob
Job
launch SupervisorJob
Job
launch SupervisorJob
Job
launch SupervisorJob
Job
launch SupervisorJob
handler
Job
launch SupervisorJob
handler
Job
launch SupervisorJob
handler
Check this out
Job
Check this out
launch SupervisorJob
handler
Job Cancel
launch SupervisorJob
handler
Job
launch SupervisorJob
Cancel
handler
Job
launch SupervisorJob
handler
Job
launch SupervisorJob
handler
jobs.forEach { it.start() }
fun main() {
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
with(scope) {
val job1 = launch {
throw Exception("Some jobs just want to watch the world burn")
}
val job2 = launch {
delay(3000)
println("I've done something extremely useful")
}
}
scope.coroutineContext[Job]?.let { job ->
runBlocking { job.children.forEach { it.join() } }
} // `job1.join()` will throw, so `it.join()` should actually be in a `try/catch` block
}
Error handling
fun main() {
// `someScope: CoroutineScope` already exists
someScope.launch { // this coroutine is a child of someScope
supervisorScope { // SupervisorJob inside
val job1 = launch {
throw Exception("Some jobs just want to watch the world burn")
}
val job2 = launch {
println("Going to do something extremely useful")
delay(3000)
println("I've done something extremely useful")
}
}
}
...
}
Error handling
fun main() {
val scopeWithHandler = CoroutineScope(CoroutineExceptionHandler {
context, error -> println("root handler called")
})
scopeWithHandler.launch {
supervisorScope {
launch { throw Exception() }
launch(CoroutineExceptionHandler { context, error ->
println("personal handler called")
}) { throw Exception() }
}
}
...
}
Exceptions are not propagated to parents meaning you can override the handler.
Inside CoroutineScope
Structured concurrency
Error handling (revisited)
● GlobalScope – This is a delicate API and its use requires care. Make sure you fully read and understand the
documentation of any declaration that is marked as a delicate API. The delicate part is that no Job is attached
to the GlobalScope, making its use dangerous and inconvenient.
In the event of a downloadContent or processContent crash, the exception goes to the coroutineScope, which stores
links to all child coroutines and will cancel them. This is an example of structured concurrency, a concept that is not
present in threads.
A helpful convention
or:
fun CoroutineScope.moreWork(...): Job = launch { ... }
Not:
suspend fun CoroutineScope.dontDoThisPlease()
A helpful convention
● The coroutine (job) does not know that somebody is trying to cancel it.
● Cancellation is cooperative.
Cancelling coroutines
Channel is like BlockingQueue, but with suspending calls instead of blocking ones.
● Channels are fair, meaning that send and receive calls are served in a first-in first-out
order.
● By default, channels have RENDEZVOUS capacity: no buffer at all. This behavior can be
tweaked: The user can specify buffer capacity, what to do when buffer overflows, and
what to do with undelivered items.
Select (experimental!)
Now that we know much more, let’s get a better approximation of what’s going on under the
hood.
Under the hood
class PostItemStateMachine(
completion: Continuation<Any?>?,
context: CoroutineContext?
): ContinuationImpl(completion) {
var result: Result<Any?> = Result(null)
var label: Int = 0
We are given:
suspend fun suspendAnswer() = 42
suspend fun suspendSqr(x: Int) = x * x
fun main() {
::suspendAnswer.startCoroutine(object : Continuation<Int> {
override val context: CoroutineContext
get() = CoroutineName("Empty Context Simulation")
...
cancellableCont.cancel(…)
}
Miscellaneous
async / await
async / await in Kotlin
● await is a single function, but depending on its environment it can result in 2 different behaviours.
● The C# approach was a great inspiration for the Kotlin team when they were designing coroutines, as it was for
Dart, TS, JS, Python, Rust, C++...
async / await in Kotlin
Deferred<T> : Job is a Job that we can get some result from. async is just another coroutine builder. You can write
exactly the same in Kotlin!
Check out developer.android.com to learn how coroutines are used (extensively) in modern
Android development.
● All of the code from this presentation can be found in the corountines folder at github.com/bochkarevko/kotlin-
things/
Thanks!