Slide 1

Slide 1 text

Grokking Coroutines @danlew42

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Versus https://www.flickr.com/photos/41061319@N00/262930800/

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

“Essentially, coroutines are light-weight threads.” ~Kotlin Documentation

Slide 9

Slide 9 text

suspend fun requestRandomUrl() = withContext(Dispatchers.IO) { … } suspend fun downloadImage(url: Url) = withContext(Dispatchers.IO) { … } fun displayImage(image: Image) myScope.launch { val url = requestRandomUrl() val image = downloadImage(url) displayImage(image) }

Slide 10

Slide 10 text

sequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } }

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

–The Design of Everyday Things “A good conceptual model allows us to predict the effects of our actions. Without a good model we operate by rote, blindly; we do operations as we were told to do them; we can’t fully appreciate why, what effects to expect, or what to do if things go wrong.”

Slide 14

Slide 14 text

What are coroutines?

Slide 15

Slide 15 text

Subroutines fun sumSquaredValues(values: List): Int { return values.sumBy { value -> square(value) } } fun square(value: Int): Int = value * value

Slide 16

Slide 16 text

Routine Subroutine

Slide 17

Slide 17 text

Coroutine Coroutine

Slide 18

Slide 18 text

val iterator = sequence.iterator() while (iterator.hasNext()) { val next = iterator.next() println(next) } sequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } Consumer Producer

Slide 19

Slide 19 text

val iterator = sequence.iterator() while (iterator.hasNext()) { val next = iterator.next() println(next) } sequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } Consumer Producer

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

class Fibonacci { var cur = 1 var next = 1 fun next(): Int { val toReturn = cur val tmp = cur + next cur = next next = tmp return toReturn } } sequence { var cur = 1 var next = 1 while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } } Versus

Slide 22

Slide 22 text

Multiple Suspend Points sequence { yield(1) // first Fibonacci number var cur = 1 var next = 1 while (true) { yield(next) // next Fibonacci number val tmp = cur + next cur = next next = tmp } }

Slide 23

Slide 23 text

Why use coroutines?

Slide 24

Slide 24 text

Why use coroutines? • Another tool for writing good code • Two ways to use tool: • Generators • Concurrency

Slide 25

Slide 25 text

https://www.flickr.com/photos/117693452@N04/12547460924

Slide 26

Slide 26 text

https://www.maxpixel.net/Lana-Frame-Threads-Thread-Handmade-Weaving-3019254

Slide 27

Slide 27 text

Blocking Functions fun main() { print(calculateMeaningOfLife()) } fun calculateMeaningOfLife(): Int { // Calculates for 7.5 million years, then... return 42 }

Slide 28

Slide 28 text

Non-Blocking Function suspend fun calculateMeaningOfLife(): Int { delay(7.5 million years) return 42 }

Slide 29

Slide 29 text

Blocking vs. Nonblocking Blocking Blocked During I/O Non-Blocking Waiting During I/O Doing other work

Slide 30

Slide 30 text

Two Tasks, One Thread suspend fun task(name: String, delay: Long) { joinAll( async { doSomething("First", 10) }, async { doSomething("Second", 5) } ) } suspend fun doSomething(name: String, delay: Long) { println("$name START (${Thread.currentThread().name})") delay(delay) println("$name END (${Thread.currentThread().name})") } First START (main) Second START (main) Second END (main) First END (main)

Slide 31

Slide 31 text

Threads == preemptive multitasking Coroutines == cooperative multitasking

Slide 32

Slide 32 text

Coroutines *are* like light- weight threads!

Slide 33

Slide 33 text

Kotlin Coroutine Basics

Slide 34

Slide 34 text

Not Basic suspend fun requestRandomUrl() = withContext(Dispatchers.IO) { … } suspend fun downloadImage(url: Url) = withContext(Dispatchers.IO) { … } fun displayImage(image: Image) myScope.launch { val url = requestRandomUrl() val image = downloadImage(url) displayImage(image) }

Slide 35

Slide 35 text

kotlin.coroutines (stdlib) kotlinx.coroutines (library)

Slide 36

Slide 36 text

kotlin.coroutines (stdlib) kotlinx.coroutines (library)

Slide 37

Slide 37 text

Suspend Keyword

Slide 38

Slide 38 text

How the !@#$ do you start a coroutine?

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

fun (suspend () -> T).startCoroutine(completion: Continuation) suspend fun mySuspendingFunction() fun main() { ::mySuspendingFunction.startCoroutine( Continuation(EmptyCoroutineContext) { } ) }

Slide 41

Slide 41 text

fun (suspend () -> T).startCoroutine(completion: Continuation) suspend fun mySuspendingFunction() fun main() { ::mySuspendingFunction.startCoroutine( Continuation(EmptyCoroutineContext) { } ) }

Slide 42

Slide 42 text

Continuations public interface Continuation { public val context: CoroutineContext public fun resumeWith(result: Result) } class Result constructor( val value: Any? )

Slide 43

Slide 43 text

Continuations public interface Continuation { public val context: CoroutineContext public fun resumeWith(result: Result) } class Result constructor( val value: Any? )

Slide 44

Slide 44 text

Continuations public interface Continuation { public val context: CoroutineContext public fun resumeWith(result: Result) } class Result constructor( val value: T?, val exception: Throwable )

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Coroutines - Under the Hood suspend fun fizzBuzz(): String | | compiles to… V fun fizzBuzz(continuation: Continuation): Any?

Slide 47

Slide 47 text

Coroutines - Under the Hood suspend fun fizzBuzz(): String | | compiles to… V fun fizzBuzz(continuation: Continuation): Any?

Slide 48

Slide 48 text

They’re Just Callbacks! Coroutines Callbacks

Slide 49

Slide 49 text

Callback Hell requestRandomUrl { url -> downloadImage(url) { image -> displayImage(image) } } myScope.launch { val url = requestRandomUrl() val image = downloadImage(url) displayImage(image) } Vs

Slide 50

Slide 50 text

Context public interface Continuation { public val context: CoroutineContext public fun resumeWith(result: Result) }

Slide 51

Slide 51 text

Context public interface Continuation { public val context: CoroutineContext public fun resumeWith(result: Result) }

Slide 52

Slide 52 text

Coroutine Basics • suspend keyword • Continuations • CoroutineContexts • …And a couple other minor things not worth mentioning here

Slide 53

Slide 53 text

Kotlin Coroutine Library

Slide 54

Slide 54 text

startCoroutine Is Ugly suspend fun mySuspendingFunction() fun main() { ::mySuspendingFunction.startCoroutine( Continuation(EmptyCoroutineContext) { } ) }

Slide 55

Slide 55 text

What We Want suspend fun requestRandomUrl() = withContext(Dispatchers.IO) { … } suspend fun downloadImage(url: Url) = withContext(Dispatchers.IO) { … } fun displayImage(image: Image) myScope.launch { val url = requestRandomUrl() val image = downloadImage(url) displayImage(image) }

Slide 56

Slide 56 text

Starting a Coroutine suspend fun requestRandomUrl() = withContext(Dispatchers.IO) { … } suspend fun downloadImage(url: Url) = withContext(Dispatchers.IO) { … } fun displayImage(image: Image) myScope.launch { val url = requestRandomUrl() val image = downloadImage(url) displayImage(image) }

Slide 57

Slide 57 text

Launch Patterns myScope.launch { doSomething() } myScope.launch { val result = doSomething() callback(result) }

Slide 58

Slide 58 text

Launch Patterns myScope.launch { doSomething() } myScope.launch { val result = doSomething() callback(result) }

Slide 59

Slide 59 text

CoroutineScope myScope.launch { doSomething() } myScope.launch { val result = doSomething() callback(result) }

Slide 60

Slide 60 text

launch { task1() } launch { task2() } launch { task3() }

Slide 61

Slide 61 text

val job1 = launch { task1() } val job2 = launch { task2() } val job3 = launch { task3() } ... job1.cancel() job2.cancel() job3.cancel()

Slide 62

Slide 62 text

val job1 = launch { task1() } val job2 = launch { task2() } val job3 = launch { task3() } ... job1.cancel() job2.cancel() job3.cancel()

Slide 63

Slide 63 text

val job = Job() val myScope = CoroutineScope(job) ... myScope.launch { task1() } myScope.launch { task2() } myScope.launch { task3() } ... job.cancel()

Slide 64

Slide 64 text

val job = Job() val myScope = CoroutineScope(job) ... myScope.launch { task1() } myScope.launch { task2() } myScope.launch { task3() } ... job.cancel()

Slide 65

Slide 65 text

val job = Job() val myScope = CoroutineScope(job) ... myScope.launch { task1() } myScope.launch { task2() } myScope.launch { task3() } ... job.cancel()

Slide 66

Slide 66 text

class MyActivity : Activity() { private val job = Job() private val myScope = CoroutineScope(job) /* ...Launch coroutines to your heart's desire... */ override fun onDestroy() { super.onDestroy() job.cancel() } }

Slide 67

Slide 67 text

Structured Concurrency • Concurrency with boundaries • Forces you to do the right thing • Explainer: https://vorpus.org/blog/notes-on-structured-concurrency-or- go-statement-considered-harmful/

Slide 68

Slide 68 text

What about threads? • Don’t want to run everything on a single thread • Writing everything non-blocking is exhausting • Can’t take advantage of multiple cores • Sometimes illegal! • Remember CoroutineContext? • Store whatever you want • Idea: store the thread!

Slide 69

Slide 69 text

Threading suspend fun requestRandomUrl() = withContext(Dispatchers.IO) { … } suspend fun downloadImage(url: Url) = withContext(Dispatchers.IO) { … } fun displayImage(image: Image) myScope.launch { val url = requestRandomUrl() val image = downloadImage(url) displayImage(image) }

Slide 70

Slide 70 text

Threading suspend fun requestRandomUrl() = withContext(Dispatchers.IO) { … } suspend fun downloadImage(url: Url) = withContext(Dispatchers.IO) { … } fun displayImage(image: Image) myScope.launch { val url = requestRandomUrl() val image = downloadImage(url) displayImage(image) }

Slide 71

Slide 71 text

Clarifying Threading • Metaphor: Coroutines are like light-weight threads • Feature: Kotlin coroutines let you choose thread for coroutines

Slide 72

Slide 72 text

Review • Coroutines are suspending functions • Suspending functions are useful for concurrency • Kotlin stdlib provides coroutine support • Kotlin coroutine library adds practical functions for coroutines

Slide 73

Slide 73 text

What Now?

Slide 74

Slide 74 text

But Wait, There’s More! • Compatibility - How do you use asynchronous non-coroutine code with coroutines? • Composition - How do you concurrently execute multiple suspending functions? • Cancellation - How do coroutines actually stop? • Exception handling - What happens when something goes wrong? • Flow - How can coroutines return multiple values asynchronously? • Channels - How do coroutines communicate between threads safely? • Actors - ???

Slide 75

Slide 75 text

More Info • Links: github.com/Kotlin/kotlinx.coroutines/#documentation • Roman Elizarov: medium.com/@elizarov • Android + Coroutines: medium.com/@objcode • Structured concurrency: vorpus.org/blog/notes-on-structured- concurrency-or-go-statement-considered-harmful/

Slide 76

Slide 76 text

@danlew42