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 full-size slide

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

    View full-size slide

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

    View full-size slide

  4. 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 full-size slide

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

    View full-size slide

  6. –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 full-size slide

  7. What are coroutines?

    View full-size slide

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

    View full-size slide

  9. Routine Subroutine

    View full-size slide

  10. Coroutine Coroutine

    View full-size slide

  11. 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 full-size slide

  12. 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 full-size slide

  13. 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 full-size slide

  14. 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 full-size slide

  15. Why use coroutines?

    View full-size slide

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

    • Two ways to use tool:

    • Generators

    • Concurrency

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  22. 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 full-size slide

  23. Threads == preemptive multitasking
    Coroutines == cooperative multitasking

    View full-size slide

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

    View full-size slide

  25. Kotlin Coroutine Basics

    View full-size slide

  26. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  29. Suspend Keyword

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  39. They’re Just Callbacks!
    Coroutines Callbacks

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  43. Coroutine Basics
    • suspend keyword

    • Continuations

    • CoroutineContexts

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

    View full-size slide

  44. Kotlin Coroutine Library

    View full-size slide

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

    View full-size slide

  46. 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 full-size slide

  47. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  57. 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 full-size slide

  58. 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 full-size slide

  59. 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 full-size slide

  60. 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 full-size slide

  61. 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 full-size slide

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

    • Feature: Kotlin coroutines let you choose thread for coroutines

    View full-size slide

  63. 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 full-size slide

  64. 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 full-size slide

  65. 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 full-size slide