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
3. See the example project
https://github.com/f-lombardo/kotlin-from-scratch
4. 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()
}
7. 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.
12. 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", "")
13. 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
14. 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!”
15. 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
18. 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)
}
}
}
19. await waits for another coroutine to complete
How to collaborate?
20. 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!")
}
}
}
}
21. 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!
22. 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!)
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 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.
26. 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).
27. 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.
28. 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)
}
29. 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.
30. 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.
31. 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.
32. 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.
33. 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()
}