Slide 1

Slide 1 text

Coroutines in Kotlin: In-depth review Dmytro Zaitsev Team Leader @ Lóhika

Slide 2

Slide 2 text

Blocking VS Non-blocking

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

v1: Sequential (Direct style) fun postItem(item: Item) {
 val token = prepareToken() / / 1
 val post = submitPost(token, item) / / 2
 processPost(post) / / 3
 }

Slide 5

Slide 5 text

v2: Callbacks (Continuation-Passing style) fun postItem(item: Item) {
 prepareTokenAsync { token -> / / 1
 submitPostAsync(token, item) { post -> / / 2
 processPost(post) / / 3
 }
 }
 } Continuation

Slide 6

Slide 6 text

v2: Callbacks (Continuation-Passing style) fun postItem(item: Item) {
 prepareTokenAsync { token -> / / 1
 submitPostAsync(token, item) { post -> / / 2
 processPost(post) / / 3
 }
 }
 } Callback hell

Slide 7

Slide 7 text

v3: Rx/Futures/Promises fun postItem(item: Item) {
 observeToken()
 .concatMap { token -> observePost(token, item) }
 .subscribe { post -> processPost(post) }
 }

Slide 8

Slide 8 text

v4: Coroutines Direct Style suspend fun postItem(item: Item) {
 val token = prepareToken() / / 1
 val post = submitPost(token, item) / / 2
 processPost(post) / / 3
 }

Slide 9

Slide 9 text

v4: Coroutines Direct Style suspend fun postItem(item: Item) {
 val token = prepareToken() / / 1
 val post = submitPost(token, item) / / 2
 processPost(post) / / 3
 }

Slide 10

Slide 10 text

v4: Coroutines Direct Style suspend fun postItem(item: Item) {
 val token = prepareToken() / / 1
 val post = submitPost(token, item) / / 2
 processPost(post) / / 3
 }

Slide 11

Slide 11 text

Profit: try/catch, loops, std-lib suspend fun postItems(items: List) { try { val token = prepareToken() items.forEach { item -> val post = submitPost(token, item) processPost(post) } } catch (e: BadTokenException) { /*…*/ } }

Slide 12

Slide 12 text

suspend fun computation that can be suspended

Slide 13

Slide 13 text

Experimental status Since Kotlin 1.1 -Xcoroutines=enable kotlin.coroutines.experimental -> kotlin.coroutines (1.3) Experimental != unstable Can and should be used in production

Slide 14

Slide 14 text

Experimental status New style of programming The design is not final and expected to change JetBrains still collects information and feedbacks Backwards compatibility guaranteed

Slide 15

Slide 15 text

Terminology coroutine suspending function suspending lambda suspending function type coroutine builder suspension point continuation

Slide 16

Slide 16 text

Terminology coroutine suspending function suspending lambda suspending function type coroutine builder suspension point continuation

Slide 17

Slide 17 text

A coroutine is… an instance of suspendable computation similar to a daemon thread, but very light-weight similar to a future or promise

Slide 18

Slide 18 text

Why coroutines? threads are expensive to keep and switch your code is single threaded you’ve got lots of mutable states

Slide 19

Slide 19 text

Patterns • generators/yield: C#, Python, Scala • async/await: C#, ECMAScript, Dart • channels, select: Go • actors: Scala

Slide 20

Slide 20 text

Standard API • Language support (`suspend` keyword) • low-level basic API (stdlib: kotlin.coroutines) • high-level APIs that can be used in user code

Slide 21

Slide 21 text

Low-level API (kotlin.coroutines) • kotlin.coroutines.experimental • create/start/suspendCoroutine() • Continuation interface • @RestrictSuspension annotation • kotlin.coroutines.experimental.intrinsics • suspendCoroutineOrReturn()

Slide 22

Slide 22 text

How does it work under the hood?

Slide 23

Slide 23 text

Continuation interface Continuation {
 val context: CoroutineContext
 fun resume(value: T)
 fun resumeWithException(exception: Throwable)
 }

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Continuations suspend fun postItem(item: Item) {
 val token = prepareToken()
 val post = submitPost(token, item)
 processPost(post)
 } Initial continuation

Slide 26

Slide 26 text

Continuations suspend fun postItem(item: Item) {
 val token = prepareToken()
 val post = submitPost(token, item)
 processPost(post)
 } Continuation

Slide 27

Slide 27 text

Continuations suspend fun postItem(item: Item) {
 val token = prepareToken()
 val post = submitPost(token, item)
 processPost(post)
 } Continuation

Slide 28

Slide 28 text

Labels suspend fun postItem(item: Item) { / / LABEL 0
 val token = prepareToken() / / LABEL 1
 val post = submitPost(token, item) / / LABEL 2
 processPost(post)
 }

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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) }
 }

Slide 31

Slide 31 text

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) }
 }

Slide 32

Slide 32 text

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:
 … }
 }

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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:
 …

Slide 35

Slide 35 text

How to create custom coroutines?

Slide 36

Slide 36 text

Await for a Single suspend fun Single.await(): T = TODO()

Slide 37

Slide 37 text

Suspend a coroutine suspend fun Single.await(): T = suspendCancellableCoroutine { cont -> / / … }

Slide 38

Slide 38 text

Subscribe suspend fun Single.await(): T = suspendCancellableCoroutine { cont -> subscribe(object : SingleObserver { override fun onSuccess(t: T) { TODO() } override fun onError(error: Throwable) { TODO() } override fun onSubscribe(d: Disposable) { TODO() } }) }

Slide 39

Slide 39 text

Return a result, if successful suspend fun Single.await(): T = suspendCancellableCoroutine { cont -> subscribe(object : SingleObserver { override fun onSuccess(t: T) { cont.resume(t) } override fun onError(error: Throwable) { TODO() } override fun onSubscribe(d: Disposable) { TODO() } }) }

Slide 40

Slide 40 text

Or resume with exception suspend fun Single.await(): T = suspendCancellableCoroutine { cont -> subscribe(object : SingleObserver { override fun onSuccess(t: T) { cont.resume(t) } override fun onError(error: Throwable) { cont.resumeWithException(error) } override fun onSubscribe(d: Disposable) { TODO() } }) }

Slide 41

Slide 41 text

Don’t forget to dispose suspend fun Single.await(): T = suspendCancellableCoroutine { cont -> subscribe(object : SingleObserver { 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() } } }) }

Slide 42

Slide 42 text

And this is it suspend fun Single.await(): T = suspendCancellableCoroutine { cont -> subscribe(object : SingleObserver { 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() } } }) }

Slide 43

Slide 43 text

kotlinx.coroutines Core integration Guava JDK 8 NIO Quasar reactive Reactor RxJava 1.x Reactive Streams RxJava 2.x UI Android JavaFX Swing

Slide 44

Slide 44 text

async promise returned (Deferred + await)

Slide 45

Slide 45 text

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)
 }

Slide 46

Slide 46 text

Not idiomatic way fun loadImageAsync(url: String): Deferred = async { TODO() } suspend fun processImage(url: String) { val image = loadImageAsync(url).await() imageCache.add(image) } Don’t define async functions in the first place

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Generators API in kotlin.coroutines kotlin.coroutines.experimental buildIterator() buildSequence()

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

launch fire and forget (Job)

Slide 55

Slide 55 text

run, runBlocking for main functions and tests

Slide 56

Slide 56 text

delay like Thread.sleep(), but non-blocking

Slide 57

Slide 57 text

Channel transfers values between coroutines

Slide 58

Slide 58 text

produce produces a stream of values by sending them to a channel (ProducerJob)

Slide 59

Slide 59 text

actor deals with it’s mailbox (ActorJob)

Slide 60

Slide 60 text

select waits for the result of multiple suspending functions

Slide 61

Slide 61 text

Job lifecycle New Active Cancelling Completed Cancelled

Slide 62

Slide 62 text

Job states State isActive isCompleted isCancelled New - - - Active ✔ - - Completed - ✔ - Cancelling - - ✔ Cancelled - ✔ ✔

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

Memory leaks

Slide 65

Slide 65 text

WeakReference “life hack” suspend operator fun WeakReference.invoke(): T = suspendCoroutineOrReturn { get() ?: throw CancellationException() } val activityRef = WeakReference(this)
 launch(CommonPool) {
 activityRef().expensiveComputation()
 }

Slide 66

Slide 66 text

Demo

Slide 67

Slide 67 text

A lot more left… Coroutine dispatchers Cancellation Mutable state and concurrency UI programming Reactive streams

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

We are hiring!

Slide 70

Slide 70 text

Thank you! @DmitriyZaitsev