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. 2.
  2. 3.
  3. 4.
  4. 5.
  5. 7.
  6. 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) }
  7. 10.

    sequence { var cur = 1 var next = 1

    while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } }
  8. 11.
  9. 12.
  10. 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.”
  11. 15.

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

    -> square(value) } } fun square(value: Int): Int = value * value
  12. 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
  13. 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
  14. 20.
  15. 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
  16. 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 } }
  17. 24.

    Why use coroutines? • Another tool for writing good code

    • Two ways to use tool: • Generators • Concurrency
  18. 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)
  19. 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) }
  20. 39.
  21. 40.

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

    fun main() { ::mySuspendingFunction.startCoroutine( Continuation(EmptyCoroutineContext) { } ) }
  22. 41.

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

    fun main() { ::mySuspendingFunction.startCoroutine( Continuation(EmptyCoroutineContext) { } ) }
  23. 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? )
  24. 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? )
  25. 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 )
  26. 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)
  27. 46.

    Coroutines - Under the Hood suspend fun fizzBuzz(): String |

    | compiles to… V fun fizzBuzz(continuation: Continuation<String>): Any?
  28. 47.

    Coroutines - Under the Hood suspend fun fizzBuzz(): String |

    | compiles to… V fun fizzBuzz(continuation: Continuation<String>): Any?
  29. 49.

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

    displayImage(image) } } myScope.launch { val url = requestRandomUrl() val image = downloadImage(url) displayImage(image) } Vs
  30. 52.

    Coroutine Basics • suspend keyword • Continuations • CoroutineContexts •

    …And a couple other minor things not worth mentioning here
  31. 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) }
  32. 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) }
  33. 61.

    val job1 = launch { task1() } val job2 =

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

    val job1 = launch { task1() } val job2 =

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

    val job = Job() val myScope = CoroutineScope(job) ... myScope.launch

    { task1() } myScope.launch { task2() } myScope.launch { task3() } ... job.cancel()
  36. 64.

    val job = Job() val myScope = CoroutineScope(job) ... myScope.launch

    { task1() } myScope.launch { task2() } myScope.launch { task3() } ... job.cancel()
  37. 65.

    val job = Job() val myScope = CoroutineScope(job) ... myScope.launch

    { task1() } myScope.launch { task2() } myScope.launch { task3() } ... job.cancel()
  38. 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() } }
  39. 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/
  40. 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!
  41. 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) }
  42. 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) }
  43. 71.

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

    Feature: Kotlin coroutines let you choose thread for coroutines
  44. 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
  45. 73.
  46. 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 - ???
  47. 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/
  48. 76.