Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Grokking Coroutines

Daniel Lew
October 05, 2019

Grokking Coroutines

What are coroutines, really?

Given at Kotlin/Everywhere MN.

Recording available here: https://www.youtube.com/watch?v=Axq8LdQqQGQ

Daniel Lew

October 05, 2019
Tweet

More Decks by Daniel Lew

Other Decks in Programming

Transcript

  1. Grokking Coroutines
    @danlew42

    View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. Versus
    https://www.flickr.com/photos/[email protected]/262930800/

    View Slide

  7. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  11. View Slide

  12. View Slide

  13. –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.”

    View Slide

  14. What are coroutines?

    View Slide

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

    View Slide

  16. Routine Subroutine

    View Slide

  17. Coroutine Coroutine

    View Slide

  18. 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

    View Slide

  19. 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

    View Slide

  20. View Slide

  21. 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

    View Slide

  22. 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
    }
    }

    View Slide

  23. Why use coroutines?

    View Slide

  24. Why use coroutines?
    • Another tool for writing good code

    • Two ways to use tool:

    • Generators

    • Concurrency

    View Slide

  25. https://www.flickr.com/photos/[email protected]/12547460924

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. 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)

    View Slide

  31. Threads == preemptive multitasking
    Coroutines == cooperative multitasking

    View Slide

  32. Coroutines *are* like light-
    weight threads!

    View Slide

  33. Kotlin Coroutine Basics

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. Suspend Keyword

    View Slide

  38. How the [email protected]#$ do you start a
    coroutine?

    View Slide

  39. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  48. They’re Just Callbacks!
    Coroutines Callbacks

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. Coroutine Basics
    • suspend keyword

    • Continuations

    • CoroutineContexts

    • …And a couple other minor things not worth mentioning here

    View Slide

  53. Kotlin Coroutine Library

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  66. 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()
    }
    }

    View Slide

  67. 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/

    View Slide

  68. 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!

    View Slide

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

    View Slide

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

    View Slide

  71. Clarifying Threading
    • Metaphor: Coroutines are like light-weight threads

    • Feature: Kotlin coroutines let you choose thread for coroutines

    View Slide

  72. Review
    • Coroutines are suspending functions

    • Suspending functions are useful for concurrency

    • Kotlin stdlib provides coroutine support

    • Kotlin coroutine library adds practical functions for coroutines

    View Slide

  73. What Now?

    View Slide

  74. 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 - ???

    View Slide

  75. 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/

    View Slide

  76. @danlew42

    View Slide