Coroutines

First

Me yesterday

Me this morning

Resources

Back to the basics

What is a function ?

A sequence of instructions that takes inputs and gives us outputs

fun makeASandwich(): Sandwich{
    val bread = Bread(seed = oat)
    val sauce = "harissa" + "mayo"
    return Sandwich(bread, sauce, "beef")
}



fun goToRestaurant(): Boolean?{
    throw YouAreBrokeDude()
    return null
}


fun eat(){
    try{ 
        goToRestaurant()
    }
    catch(error: YouAreBrokeDude){
        makeASandwich()
    }
}

eat()

A thread describe in which context this sequence of instructions should be executed

//Thread 1

println("Hello world")
var x = 3
x *= x
println("The result is $x")
//Thread 2

println("Hello world from another thread")
var x = 5
x *= x
println("The result is $x from the other thread")

Why do we care anyway, can't we run everything in the same thread  ?

doSomething()


callAPI() //Take time


doSomethingElse()

Why not use multi-threading then?

Because it's costly, hard to maintain and consume your hardware resources

Coroutines

  • Same as threads they are able to execute instructions
  • Coroutines are executed within a thread meaning you can run thousands of coroutines without crashing your app
  • They are suspendable => start the execution of a coroutine, pause the execution and resume it later
  • coroutine have their own context which you as coder have control of.

How to make a function callable in a coroutine context

fun doSomething(){
	//...
}
suspend fun doSomething(){
	//...
}

suspend functions can only be called from coroutines or other functions with suspension points. They cannot be called by normal code

How to start a coroutine ?

launch {
	doSomething()
}

⚠️Spoiler Alert⚠️

DO NOT USE WHAT YOU WILL SEE IN PRODUCTION

DO NOT USE WHAT YOU WILL SEE IN PRODUCTION

DO NOT USE WHAT YOU WILL SEE IN PRODUCTION

Previously on Amc's the Walking dead

Core concepts

  • Same as threads they are able to execute instructions
  • Coroutines are executed within a thread meaning you can run thousands of coroutines without crashing your app
  • They are suspendable => start the execution of a coroutine, pause the execution and resume it later
  • coroutine have their own context which you as coder have control of.

How to coroutine

suspend fun doSomething(){
	//...
}


launch {
	doSomething()
}
fun doSomething(){
	//...
}

doSomething()

Start a coroutine

fun main(args: Array<String>) {
    launch { // launch new coroutine in background and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello,") // main thread continues while coroutine is delayed
    Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

Start a coroutine

fun main(args: Array<String>) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}

The main difference between async and launch is that launch is used for starting a computation that isn't expected to return a specific result. launch returns Job, which represents the coroutine. It is possible to wait until it completes by calling Job.join() but rather use async in that case.

Start a coroutine

suspend fun demo() : Any = TODO()

fun main(args: Array<String>) {
  // demo() // this alone wouldn't compile... Error:() Kotlin: Suspend function 'demo' should be called only from a coroutine or another suspend function
  // whereas the following works as intended:
  runBlocking {
    demo()
  } // it also waits until demo()-call is finished which wouldn't happen if you use launch
}

runBlocking is used as a bridge between regular and suspend functions, between blocking and non-blocking worlds. It works as an adaptor for starting the top-level main coroutine and is intended primarily to be used in main functions and in tests.

PLEASE AVOID IT WHEN YOU ARE UNSURE

Coroutines run on the same thread, unless you tell otherwise

async{ ... }
async(Dispatchers.DEFAULT){ ... }

Use dispatcher from the outer scope

Use a shared pool on the JVM

Dispatchers

  • Dispatchers.Default - for CPU intense work (e.g. sorting a big list)
  • Dispatchers.Main - what this will be depends on what you've added to your programs runtime dependencies (e.g. kotlinx-coroutines-android, for the UI thread in Android)
  • Dispatchers.Unconfined - runs coroutines unconfined on no specific thread
  • Dispatchers.IO - for heavy IO work (e.g. long-running database queries)

Coroutine Context

Every coroutine in Kotlin has a context that is represented by an instance of CoroutineContext interface. A context is a set of elements and current coroutine context is available via coroutineContext property:

fun main() = runBlocking<Unit> {
    println("My context is: $coroutineContext")
}

// My context is: [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@5b80350b, BlockingEventLoop@5d6f64b1]

Coroutine Scope

There is also an interface called CoroutineScope that consists of a sole property — val coroutineContext: CoroutineContext. It has nothing else but a context

public interface CoroutineScope {
    /**
     * The context of this scope.
     * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
     * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
     *
     * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
     */
    public val coroutineContext: CoroutineContext
}

Guess the output

package coroutines

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {

    println("🍔")

    runBlocking() {
        launch {
            delay(1000)
            println("🍕")
        }

        launch {
            delay(500)
            println("🍰")
        }
    }

    println("🍦")
}

Guess the output

package coroutines

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.GlobalScope

fun main(){

    println("🍔")

    runBlocking() {
        GlobalScope.launch {
            delay(1000)
            println("🍕")
        }

        GlobalScope.launch {
            delay(500)
            println("🍰")
        }
    }

    println("🍦")

}
Made with Slides.com