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

Kotlin from scratch 3 - Coroutines

Kotlin from scratch 3 - Coroutines

What is a Coroutine in Kotlin? Which are the differences with threads? What is collaborative concurrency? Have a look at these slides and at the companion Github repository https://github.com/f-lombardo/kotlin-from-scratch

Franco Lombardo

June 19, 2020
Tweet

More Decks by Franco Lombardo

Other Decks in Programming

Transcript

  1. What is a thread? A thread is the smallest sequence

    of programmed instructions that can be managed independently by the operating system scheduler. (Wikipedia) fun main(args: Array<String>) { thread(start = true, name = "Calandrino", isDaemon = true) { while(true) { logMsg("So' bischero!") BigInteger.probablePrime(3072, Random()) } } Thread { while(true) { logMsg("Io pitto!") BigInteger.probablePrime(3072, Random()) } }.apply { name = "Buffalmacco" }.start() }
  2. What is a coroutine? Coroutines generalize subroutines for non-preemptive multitasking,

    by allowing execution to be suspended and resumed. Coroutines are very similar to threads. However, coroutines are cooperatively multitasked, whereas threads are typically preemptively multitasked. This means that coroutines provide concurrency but not parallelism. (Wikipedia) Subroutines: objects defined by the programming language, not by the operating system.
  3. How to create a coroutine? 1) In build.gradle add the

    dependency to org.jetbrains.kotlinx:kotlinx-coroutines-core:version_nbr 2) There is a system property to set if you want to see more coroutine infos System.setProperty("kotlinx.coroutines.debug", "")
  4. How to create a coroutine? fun main(args: Array<String>) = runBlocking

    { async(CoroutineName("Calandrino")) { while (true) { logMsg("So' bischero!") BigInteger.probablePrime(1024, Random()) } } async(CoroutineName("Buffalmacco")) { while (true) { logMsg("Io pitto!") BigInteger.probablePrime(1024, Random()) } } } runBlocking, async (and launch) are coroutines builders
  5. It doesn’t work as expected! fun main(args: Array<String>) = runBlocking

    { async(CoroutineName("Calandrino")) { while (true) { logMsg("So' bischero!") BigInteger.probablePrime(1024, Random()) } } async(CoroutineName("Buffalmacco")) { while (true) { logMsg("Io pitto!") BigInteger.probablePrime(1024, Random()) } } } It just prints “So’ bischero!”
  6. Remember: we have to collaborate! fun main(args: Array<String>) = runBlocking

    { async(CoroutineName("Calandrino")) { while (true) { logMsg("So' bischero!") BigInteger.probablePrime(1024, Random()) yield() } } async(CoroutineName("Buffalmacco")) { while (true) { logMsg("Io pitto!") BigInteger.probablePrime(1024, Random()) yield() } } } yield gives control to other coroutines
  7. Collaborating using delay fun main(args: Array<String>) = runBlocking { async(CoroutineName("Calandrino"))

    { while (true) { logMsg("So' bischero!") BigInteger.probablePrime(1024, Random()) delay(10) } } async(CoroutineName("Buffalmacco")) { while (true) { logMsg("Io pitto!") BigInteger.probablePrime(1024, Random()) delay(10) } } }
  8. Collaborating using await fun main(args: Array<String>) { runBlocking { async(CoroutineName("Calandrino"))

    { while (true) { val deferredNumber: Deferred<BigInteger> = async { bigPrime() } logMsg("Bischerata numero ${deferredNumber.await()}") } } async(CoroutineName("Buffalmacco")) { while (true) { val deferredNumber: Deferred<BigInteger> = async { bigPrime() } logMsg("Clandrino è bischero ${deferredNumber.await()} volte!") } } } }
  9. Collaborating using await fun main(args: Array<String>) { runBlocking { async(CoroutineName("Calandrino"))

    { while (true) { logMsg("Bischerata numero ${bigPrime()}") } } async(CoroutineName("Buffalmacco")) { while (true) { logMsg("Calandrino è bischero ${bigPrime()} volte!") } } } } It doesn’t work because Calandrino keeps the thread!
  10. Collaborating using await fun main(args: Array<String>) { runBlocking { async(CoroutineName("Calandrino"))

    { while (true) { logMsg("Bischerata numero ${bigPrime()}") } } async(CoroutineName("Buffalmacco")) { while (true) { logMsg("Calandrino è bischero ${bigPrime()} volte!") } } } } It doesn’t work because Calandrino keeps the thread! (Bischero!)
  11. suspend fun calandrinate() { while (true) { logMsg("So' bischero!") BigInteger.probablePrime(1024,

    Random()) yield() } } async(CoroutineName("Calandrino")) { calandrinate() } Collaborating functions are suspending ones async(CoroutineName("Calandrino")) { while (true) { logMsg("So' bischero!") BigInteger.probablePrime(1024, Random()) yield() } }
  12. suspend fun calandrinate() { while (true) { logMsg("So' bischero!") BigInteger.probablePrime(1024,

    Random()) yield() } } Collaborating functions are suspending ones The suspend modifier does not make a function either asynchronous or non-blocking. It just tells that the function could be suspended, as it could call other suspending functions (such as yield or delay). Anyway, it’s a good convention to create suspending functions that don’t block the calling thread.
  13. async and launch builders need a scope! CoroutineScope(Dispatchers.Unconfined + CoroutineName("Calandrino")).async

    { bigPrime(1024) } The async and launch coroutines builders are extension functions for the CoroutineScope interface, so they must be called on an object of that type. When you see them called without an explicit scope, as inside runBlocking, it is because they are called in a block that provides an implicit this receiver of type CoroutineScope.
  14. async and launch builders need a scope! CoroutineScope(Dispatchers.Unconfined + CoroutineName("Calandrino")).async

    { bigPrime(1024) } A CoroutineScope holds information on how to run coroutines, using a CoroutineContext object. We can create one of them with: • the CoroutineScope factory function; • the MainScope factory function: this works for Android, Swing and JavaFx applications, pointing to the Main UI thread; • the GlobalScope object, for top-level coroutines which are operating on the whole application lifetime; • the runBlocking function, that creates an implicit CoroutinesScope receiver; • the coroutineScope function (inside a suspend function/coroutine).
  15. async and launch builders need a scope! CoroutineScope(Dispatchers.Unconfined + CoroutineName("Calandrino")).async

    { bigPrime(1024) } A CoroutineContext is something like a “set” of properties related to the coroutine. Each element of a CoroutineContext is a CoroutineContext too. Elements can be added with the + operator. So, whenever we have a CoroutineContext parameter, we can pass just one type of information, that will override the existing one, or a concatenated set of elements.
  16. async and launch builders need a scope! Whenever we have

    a CoroutineContext parameter, we can pass just one type of information, that will override the existing one, or a concatenated set of elements. The type of these three expression is CoroutineContext: • Dispatchers.Unconfined • CoroutineName("Calandrino") • Dispatchers.Unconfined + CoroutineName("Calandrino") CoroutineScope(Dispatchers.Unconfined + CoroutineName("Calandrino")).async { bigPrime(1024) }
  17. async and launch builders need a scope! CoroutineScope(Dispatchers.Unconfined + CoroutineName("Calandrino")).async

    { bigPrime(1024) } An important type of CoroutineContext element is the Dispatcher, that defines the thread running the coroutine.
  18. async and launch builders need a scope! CoroutineScope(Dispatchers.Unconfined + CoroutineName("Calandrino")).async

    { bigPrime(1024) } Types of dispatchers: • Dispatcher.Default: backed by a shared pool of threads with maximum size equal to the number of CPU cores; for CPU/bound tasks; • Dispatcher.IO: for offloading blocking IO tasks to a shared pool of threads (the default is 64); • Dispatcher.Main: main UI thread in Android/Swing applications; • Dispathcer.Unconfined: not confined to any specific thread. The coroutine executes in the current thread first and lets the coroutine resume in whatever thread.
  19. async and launch builders need a scope! CoroutineScope(Dispatchers.Unconfined + CoroutineName("Calandrino")).async

    { bigPrime(1024) } Dispatcher can also be created with these functions: • newSingleThreadContext: one thread per coroutine; • newFixedThreadPoolContex: a fixed size thread pool; • asCoroutineDispatcher extension function on Java Executor.
  20. The launch builder fun main(args: Array<String>) = runBlocking { val

    job: Job = launch(Dispatchers.Unconfined) { ConsoleProgressBar().showContinuously() } println("A big prime number: ${bigPrime(2048)}") job.cancel() } The launch builder returns a Job that can be used to control the execution of the coroutine.
  21. Parent-child hierarchies for Jobs Parent jobs wait the end of

    all their children Cancellation of a parent leads to immediate cancellation of all its children fun main(args: Array<String>) = runBlocking { val job: Job = launch(Dispatchers.Unconfined) { launch { launch { delayAndLog() } delayAndLog() } delayAndLog() } println("Here is a big prime number: ${bigPrime(2048)}") job.cancel() }
  22. Why coroutines? (1..10_000).forEach { thread(start = true) { while (true)

    Thread.sleep(10_000) } } By the way: it works on Windows! J
  23. Good reads Kotlin Coroutines in Android - Basics https://medium.com/swlh/kotlin-coroutines-in-android-basics-9904c98d4714 Concurrency

    is not parallelism https://kotlinexpertise.com/kotlin-coroutines-concurrency/ Blocking threads, suspending coroutines https://medium.com/@elizarov/blocking-threads-suspending-coroutines-d33e11bf4761 Coroutines: Suspending State Machines https://medium.com/google-developer-experts/coroutines-suspending-state-machines-36b189f8aa60 Coroutines hands-on https://play.kotlinlang.org/hands-on/Introduction%20to%20Coroutines%20and%20Channels/