Grokking Coroutines

D225ebf0faa666ac7655cc7e4689283c?s=47 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

D225ebf0faa666ac7655cc7e4689283c?s=128

Daniel Lew

October 05, 2019
Tweet

Transcript

  1. Grokking Coroutines @danlew42

  2. None
  3. None
  4. None
  5. None
  6. Versus https://www.flickr.com/photos/41061319@N00/262930800/

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

  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) }
  10. sequence { var cur = 1 var next = 1

    while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } }
  11. None
  12. None
  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.”
  14. What are coroutines?

  15. Subroutines fun sumSquaredValues(values: List<Int>): Int { return values.sumBy { value

    -> square(value) } } fun square(value: Int): Int = value * value
  16. Routine Subroutine

  17. Coroutine Coroutine

  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
  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
  20. None
  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
  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 } }
  23. Why use coroutines?

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

    • Two ways to use tool: • Generators • Concurrency
  25. https://www.flickr.com/photos/117693452@N04/12547460924

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

  27. Blocking Functions fun main() { print(calculateMeaningOfLife()) } fun calculateMeaningOfLife(): Int

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

    return 42 }
  29. Blocking vs. Nonblocking Blocking Blocked During I/O Non-Blocking Waiting During

    I/O Doing other work
  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)
  31. Threads == preemptive multitasking Coroutines == cooperative multitasking

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

  33. Kotlin Coroutine Basics

  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) }
  35. kotlin.coroutines (stdlib) kotlinx.coroutines (library)

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

  37. Suspend Keyword

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

  39. None
  40. fun <T> (suspend () -> T).startCoroutine(completion: Continuation<T>) suspend fun mySuspendingFunction()

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

    fun main() { ::mySuspendingFunction.startCoroutine( Continuation(EmptyCoroutineContext) { } ) }
  42. Continuations public interface Continuation<in T> { public val context: CoroutineContext

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

    public fun resumeWith(result: Result<T>) } class Result<out T> constructor( val value: Any? )
  44. Continuations public interface Continuation<in T> { public val context: CoroutineContext

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

    } fun <T> Continuation<T>.resume(value: T) fun <T> Continuation<T>.resumeWithException(exception: Throwable)
  46. Coroutines - Under the Hood suspend fun fizzBuzz(): String |

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

    | compiles to… V fun fizzBuzz(continuation: Continuation<String>): Any?
  48. They’re Just Callbacks! Coroutines Callbacks

  49. Callback Hell requestRandomUrl { url -> downloadImage(url) { image ->

    displayImage(image) } } myScope.launch { val url = requestRandomUrl() val image = downloadImage(url) displayImage(image) } Vs
  50. Context public interface Continuation<in T> { public val context: CoroutineContext

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

    public fun resumeWith(result: Result<T>) }
  52. Coroutine Basics • suspend keyword • Continuations • CoroutineContexts •

    …And a couple other minor things not worth mentioning here
  53. Kotlin Coroutine Library

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

    Continuation(EmptyCoroutineContext) { } ) }
  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) }
  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) }
  57. Launch Patterns myScope.launch { doSomething() } myScope.launch { val result

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

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

    doSomething() callback(result) }
  60. launch { task1() } launch { task2() } launch {

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

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

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

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

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

    { task1() } myScope.launch { task2() } myScope.launch { task3() } ... job.cancel()
  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() } }
  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/
  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!
  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) }
  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) }
  71. Clarifying Threading • Metaphor: Coroutines are like light-weight threads •

    Feature: Kotlin coroutines let you choose thread for coroutines
  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
  73. What Now?

  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 - ???
  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/
  76. @danlew42