KEMBAR78
Kotlin from-scratch 3 - coroutines | PDF
Kotlin from scratch 3
franco.lombardo@smeup.com - https://www.linkedin.com/in/francolombardo/
Coroutines
Danger zone!!!
Photo by Vladyslav Cherkasenko on Unsplash
See the example project
https://github.com/f-lombardo/kotlin-from-scratch
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()
}
Threads are operating system objects
At least on *nix systems!
Threads are operating system objects
And on IBM i / AS400! (WRKJVMJOB command)
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.
Parallelism
Photo by kakei lam on Unsplash
Parallelism
Calandrino thread Calandrino’s computation
Buffalmacco thread Buffalmaccos’s computation
Concurrency
By Backlit - Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=12225421
Concurrency
Main thread Calandrino Buffalmacco
Suspended coroutines
Calandrino Buffalmacco
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", "")
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
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!”
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
How to collaborate?
yield gives control to other coroutines
How to collaborate?
delay is like Thread.sleep
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)
}
}
}
await waits for another coroutine to complete
How to collaborate?
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!")
}
}
}
}
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!
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!)
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()
}
}
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.
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.
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).
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.
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)
}
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.
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.
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.
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.
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()
}
Why coroutines?
(1..100_000).forEach {
launch {
while (true) delay(10_000)
}
}
Why coroutines?
(1..10_000).forEach {
thread(start = true) {
while (true) Thread.sleep(10_000)
}
}
Why coroutines?
(1..10_000).forEach {
thread(start = true) {
while (true) Thread.sleep(10_000)
}
}
By the way: it works on Windows! J
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/

Kotlin from-scratch 3 - coroutines

  • 1.
    Kotlin from scratch3 franco.lombardo@smeup.com - https://www.linkedin.com/in/francolombardo/ Coroutines
  • 2.
    Danger zone!!! Photo byVladyslav Cherkasenko on Unsplash
  • 3.
    See the exampleproject https://github.com/f-lombardo/kotlin-from-scratch
  • 4.
    What is athread? 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() }
  • 5.
    Threads are operatingsystem objects At least on *nix systems!
  • 6.
    Threads are operatingsystem objects And on IBM i / AS400! (WRKJVMJOB command)
  • 7.
    What is acoroutine? 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.
  • 8.
  • 9.
    Parallelism Calandrino thread Calandrino’scomputation Buffalmacco thread Buffalmaccos’s computation
  • 10.
    Concurrency By Backlit -Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=12225421
  • 11.
    Concurrency Main thread CalandrinoBuffalmacco Suspended coroutines Calandrino Buffalmacco
  • 12.
    How to createa 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", "")
  • 13.
    How to createa 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
  • 14.
    It doesn’t workas 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!”
  • 15.
    Remember: we haveto 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
  • 16.
    How to collaborate? yieldgives control to other coroutines
  • 17.
    How to collaborate? delayis like Thread.sleep
  • 18.
    Collaborating using delay funmain(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) } } }
  • 19.
    await waits foranother coroutine to complete How to collaborate?
  • 20.
    Collaborating using await funmain(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!") } } } }
  • 21.
    Collaborating using await funmain(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!
  • 22.
    Collaborating using await funmain(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!)
  • 23.
    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() } }
  • 24.
    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.
  • 25.
    async and launchbuilders 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.
  • 26.
    async and launchbuilders 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).
  • 27.
    async and launchbuilders 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.
  • 28.
    async and launchbuilders 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) }
  • 29.
    async and launchbuilders 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.
  • 30.
    async and launchbuilders 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.
  • 31.
    async and launchbuilders 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.
  • 32.
    The launch builder funmain(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.
  • 33.
    Parent-child hierarchies forJobs 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() }
  • 34.
    Why coroutines? (1..100_000).forEach { launch{ while (true) delay(10_000) } }
  • 35.
    Why coroutines? (1..10_000).forEach { thread(start= true) { while (true) Thread.sleep(10_000) } }
  • 36.
    Why coroutines? (1..10_000).forEach { thread(start= true) { while (true) Thread.sleep(10_000) } } By the way: it works on Windows! J
  • 37.
    Good reads Kotlin Coroutinesin 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/