KEMBAR78
Coroutines in Kotlin. In-depth review | PDF
Coroutines in Kotlin:
In-depth review
Dmytro Zaitsev
Team Leader @ Lóhika
Blocking VS Non-blocking
v1: Sequential
(Direct style)
fun postItem(item: Item) {

val token = prepareToken() // 1

val post = submitPost(token, item) // 2

processPost(post) // 3

}
v2: Callbacks
(Continuation-Passing style)
fun postItem(item: Item) {

prepareTokenAsync { token -> // 1

submitPostAsync(token, item) { post -> // 2

processPost(post) // 3

}

}

}
Continuation
v2: Callbacks
(Continuation-Passing style)
fun postItem(item: Item) {

prepareTokenAsync { token -> // 1

submitPostAsync(token, item) { post -> // 2

processPost(post) // 3

}

}

}
Callback hell
v3: Rx/Futures/Promises
fun postItem(item: Item) {

observeToken()

.concatMap { token -> observePost(token, item) }

.subscribe { post -> processPost(post) }

}
v4: Coroutines Direct Style
suspend fun postItem(item: Item) {

val token = prepareToken() // 1

val post = submitPost(token, item) // 2

processPost(post) // 3

}
v4: Coroutines Direct Style
suspend fun postItem(item: Item) {

val token = prepareToken() // 1

val post = submitPost(token, item) // 2

processPost(post) // 3

}
v4: Coroutines Direct Style
suspend fun postItem(item: Item) {

val token = prepareToken() // 1

val post = submitPost(token, item) // 2

processPost(post) // 3

}
Profit: try/catch, loops, std-lib
suspend fun postItems(items: List<Item>) {
try {
val token = prepareToken()
items.forEach { item ->
val post = submitPost(token, item)
processPost(post)
}
} catch (e: BadTokenException) { /*…*/ }
}
suspend fun
computation that can be suspended
Experimental status
Since Kotlin 1.1
-Xcoroutines=enable
kotlin.coroutines.experimental -> kotlin.coroutines (1.3)
Experimental != unstable
Can and should be used in production
Experimental status
New style of programming
The design is not final and expected to change
JetBrains still collects information and feedbacks
Backwards compatibility guaranteed
Terminology
coroutine
suspending functionsuspending lambda
suspending function type
coroutine builder
suspension point
continuation
Terminology
coroutine
suspending function
suspending lambda
suspending function type
coroutine builder
suspension pointcontinuation
A coroutine is…
an instance of suspendable computation
similar to a daemon thread, but very light-weight
similar to a future or promise
Why coroutines?
threads are expensive to keep and switch
your code is single threaded
you’ve got lots of mutable states
Patterns
• generators/yield: C#, Python, Scala
• async/await: C#, ECMAScript, Dart
• channels, select: Go
• actors: Scala
Standard API
• Language support (`suspend` keyword)
• low-level basic API (stdlib: kotlin.coroutines)
• high-level APIs that can be used in user code
Low-level API
(kotlin.coroutines)
• kotlin.coroutines.experimental
• create/start/suspendCoroutine()
• Continuation interface
• @RestrictSuspension annotation
• kotlin.coroutines.experimental.intrinsics
• suspendCoroutineOrReturn()
How does it work under the
hood?
Continuation
interface Continuation<in T> {

val context: CoroutineContext

fun resume(value: T)

fun resumeWithException(exception: Throwable)

}
Continuation
// Kotlin
suspend fun submitPost(
token: Token,
item: Item): Post {…}
// Java/JVM
Object submitPost(
Token token,
Item item,
Continuation<Post> cont) {…}
compiler magic
Continuations
suspend fun postItem(item: Item) {

val token = prepareToken()

val post = submitPost(token, item)

processPost(post)

}
Initial continuation
Continuations
suspend fun postItem(item: Item) {

val token = prepareToken()

val post = submitPost(token, item)

processPost(post)

}
Continuation
Continuations
suspend fun postItem(item: Item) {

val token = prepareToken()

val post = submitPost(token, item)

processPost(post)

}
Continuation
Labels
suspend fun postItem(item: Item) {
// LABEL 0

val token = prepareToken()
// LABEL 1

val post = submitPost(token, item)
// LABEL 2

processPost(post)

}
Labels
suspend fun postItem(item: Item) {
switch(label) {
case 0:
val token = prepareToken()
case 1:

val post = submitPost(token, item)
case 2:

processPost(post)
}

}
State
suspend fun postItem(item: Item) {
val stateMachine = object: CoroutineImpl {…}
switch(stateMachine.label) {
case 0:
val token = prepareToken()
case 1:

val post = submitPost(token, item)
case 2:

processPost(post)
}

}
CPS Transform
fun postItem(item: Item, cont: Continuation) {
val stateMachine = object: CoroutineImpl {…}
switch(stateMachine.label) {
case 0:
val token = prepareToken(stateMachine)
case 1:

val post = submitPost(token, item, stateMachine)
case 2:

processPost(post)
}

}
Save state
fun postItem(item: Item, cont: Continuation) {
val stateMachine = object: CoroutineImpl {…}
switch(stateMachine.label) {
case 0:
stateMachine.item = item
stateMachine.label = 1
prepareToken(stateMachine)
case 1:

…
}

}
Callback
fun postItem(item: Item, cont: Continuation) {
val stateMachine = cont as? ThisSM ?: object: ThisSM {
fun resume(…) {
postItem(null, this)
}
}
switch(stateMachine.label) {
case 0:
….
}

}
Restore state and continue
fun postItem(item: Item, cont: Continuation) {
…
case 0:
stateMachine.item = item
stateMachine.label = 1
prepareToken(stateMachine)
case 1:
val item = stateMachine.item
val token = stateMachine.result
stateMachine.label = 2

submitPost(token, item, stateMachine)
case 2:

…
How to create custom
coroutines?
Await for a Single
suspend fun <T> Single<T>.await(): T = TODO()
Suspend a coroutine
suspend fun <T> Single<T>.await(): T = suspendCancellableCoroutine { cont ->
// …
}
Subscribe
suspend fun <T> Single<T>.await(): T = suspendCancellableCoroutine { cont ->
subscribe(object : SingleObserver<T> {
override fun onSuccess(t: T) {
TODO()
}
override fun onError(error: Throwable) {
TODO()
}
override fun onSubscribe(d: Disposable) {
TODO()
}
})
}
Return a result, if successful
suspend fun <T> Single<T>.await(): T = suspendCancellableCoroutine { cont ->
subscribe(object : SingleObserver<T> {
override fun onSuccess(t: T) {
cont.resume(t)
}
override fun onError(error: Throwable) {
TODO()
}
override fun onSubscribe(d: Disposable) {
TODO()
}
})
}
Or resume with exception
suspend fun <T> Single<T>.await(): T = suspendCancellableCoroutine { cont ->
subscribe(object : SingleObserver<T> {
override fun onSuccess(t: T) {
cont.resume(t)
}
override fun onError(error: Throwable) {
cont.resumeWithException(error)
}
override fun onSubscribe(d: Disposable) {
TODO()
}
})
}
Don’t forget to dispose
suspend fun <T> Single<T>.await(): T = suspendCancellableCoroutine { cont ->
subscribe(object : SingleObserver<T> {
override fun onSuccess(t: T) {
cont.resume(t)
}
override fun onError(error: Throwable) {
cont.resumeWithException(error)
}
override fun onSubscribe(d: Disposable) {
cont.invokeOnCompletion { d.dispose() }
}
})
}
And this is it
suspend fun <T> Single<T>.await(): T = suspendCancellableCoroutine { cont ->
subscribe(object : SingleObserver<T> {
override fun onSuccess(t: T) {
cont.resume(t)
}
override fun onError(error: Throwable) {
cont.resumeWithException(error)
}
override fun onSubscribe(d: Disposable) {
cont.invokeOnCompletion { d.dispose() }
}
})
}
kotlinx.coroutines
Core
integration
Guava
JDK 8
NIO
Quasar
reactive
Reactor
RxJava 1.x
Reactive Streams
RxJava 2.x
UI
Android
JavaFX
Swing
async
promise returned
(Deferred + await)
async/await
// C# way
async Task ProcessImage(String url)

{

var image = await LoadImage(url);

imageCache.Add(image);

}
// Kotlin way
suspend fun processImage(url: String) {

val image = loadImageAsync(url).await()

imageCache.add(image)

}
Not idiomatic way
fun loadImageAsync(url: String): Deferred<Image> = async { TODO() }
suspend fun processImage(url: String) {
val image = loadImageAsync(url).await()
imageCache.add(image)
}
Don’t define async functions
in the first place
Idiomatic way
suspend fun loadImage(url: String): Image = TODO()
suspend fun processImage(url: String) {
val image = async { loadImage(url) }.await()
imageCache.add(image)
}
Keep concurrency explicit
Generators API in
kotlin.coroutines
kotlin.coroutines.experimental
buildIterator()
buildSequence()
buildSequence {

print(“Start: ")

var prev = 1; var cur = 1

while (true) {
print(“Next")

yield(prev) // suspension point
val next = prev + cur

prev = cur; cur = next

}

print("End") // unreachable code

}.take(6).forEach { print(" $it ") }

// Output: Start 1 Next 1 Next 2 Next 3 Next 5 Next 8
buildSequence {

print(“Start: ")

var prev = 1; var cur = 1

while (true) {
print(“Next")

yield(prev) // suspension point
val next = prev + cur

prev = cur; cur = next

}

print("End") // unreachable code

}.take(6).forEach { print(" $it ") }

// Output: Start 1 Next 1 Next 2 Next 3 Next 5 Next 8
buildSequence {

print(“Start: ")

var prev = 1; var cur = 1

while (true) {
print(“Next")

yield(prev) // suspension point
val next = prev + cur

prev = cur; cur = next

}

print("End") // unreachable code

}.take(6).forEach { print(" $it ") }

// Output: Start 1 Next 1 Next 2 Next 3 Next 5 Next 8
buildSequence {

print(“Start: ")

var prev = 1; var cur = 1

while (true) {
print(“Next")

yield(prev) // suspension point
val next = prev + cur

prev = cur; cur = next

}

print("End") // unreachable code

}.take(6).forEach { print(" $it ") }

// Output: Start 1 Next 1 Next 2 Next 3 Next 5 Next 8
buildSequence {

print(“Start: ")

var prev = 1; var cur = 1

while (true) {
print(“Next")

yield(prev) // suspension point
val next = prev + cur

prev = cur; cur = next

}

print("End") // unreachable code

}.take(6).forEach { print(" $it ") }

// Output: Start 1 Next 1 Next 2 Next 3 Next 5 Next 8
launch
fire and forget
(Job)
run,
runBlocking
for main functions
and tests
delay
like Thread.sleep(),
but non-blocking
Channel
transfers values
between coroutines
produce
produces a stream of values
by sending them to a channel
(ProducerJob)
actor
deals with it’s mailbox
(ActorJob)
select
waits for the result of
multiple suspending
functions
Job lifecycle
New Active
Cancelling
Completed
Cancelled
Job states
State isActive isCompleted isCancelled
New
- - -
Active ✔ - -
Completed
- ✔ -
Cancelling
- - ✔
Cancelled
- ✔ ✔
val deferred = async(CommonPool) {

throw SomeException("I'm thrown inside a coroutine")

}

try {

deferred.await() // re-throws

} catch (e: SomeException) {

log(e.message)

}
Exception handling
Memory leaks
WeakReference “life hack”
suspend operator fun <T> WeakReference<T>.invoke(): T
= suspendCoroutineOrReturn {
get() ?: throw CancellationException()
}
val activityRef = WeakReference(this)

launch(CommonPool) {

activityRef().expensiveComputation()

}
Demo
A lot more left…
Coroutine dispatchers
Cancellation
Mutable state and concurrency
UI programming
Reactive streams
Links
Andrey Breslav FAQ:

https://discuss.kotlinlang.org/t/experimental-status-of-coroutines-in-1-1-and-
related-compatibility-concerns/2236
Design document (KEEP):

https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-
informal.md
Full kotlinx.coroutines API:

http://kotlin.github.io/kotlinx.coroutines
Coroutines guide by Roman ELizarov:

https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md
We are hiring!
Thank you!
@DmitriyZaitsev

Coroutines in Kotlin. In-depth review