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

Grokking Coroutines

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Daniel Lew 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

Avatar for Daniel Lew

Daniel Lew

October 05, 2019
Tweet

More Decks by Daniel Lew

Other Decks in Programming

Transcript

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

    while (true) { yield(cur) val tmp = cur + next cur = next next = tmp } }
  3. –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.”
  4. Subroutines fun sumSquaredValues(values: List<Int>): Int { return values.sumBy { value

    -> square(value) } } fun square(value: Int): Int = value * value
  5. 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
  6. 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
  7. 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
  8. 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 } }
  9. Why use coroutines? • Another tool for writing good code

    • Two ways to use tool: • Generators • Concurrency
  10. 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)
  11. 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) }
  12. fun <T> (suspend () -> T).startCoroutine(completion: Continuation<T>) suspend fun mySuspendingFunction()

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

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

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

    public fun resumeWith(result: Result<T>) } class Result<out T> constructor( val value: Any? )
  16. 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 )
  17. Continuations public interface Continuation<in T> { public val context: CoroutineContext

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

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

    | compiles to… V fun fizzBuzz(continuation: Continuation<String>): Any?
  20. Callback Hell requestRandomUrl { url -> downloadImage(url) { image ->

    displayImage(image) } } myScope.launch { val url = requestRandomUrl() val image = downloadImage(url) displayImage(image) } Vs
  21. Coroutine Basics • suspend keyword • Continuations • CoroutineContexts •

    …And a couple other minor things not worth mentioning here
  22. 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) }
  23. 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) }
  24. val job1 = launch { task1() } val job2 =

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

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

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

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

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

    Feature: Kotlin coroutines let you choose thread for coroutines
  35. Review • Coroutines are suspending functions • Suspending functions are

    useful for concurrency • Kotlin stdlib provides coroutine support • Kotlin coroutine library adds practical functions for coroutines
  36. 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 - ???
  37. 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/