kotlinx.coroutines core tutorial

This is a DRAFT of a very short tutorial on core features of kotlinx.coroutines.

Your first coroutine

Run the following code:

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

You can get full code with imports here

Run this code:

Hello,
World!

Essentially, coroutines are light-weight threads. You can achieve the same result replacing launch(Here) { ... } with thread { ... } and delay(...) with Thread.sleep(...). Try it.

If you start by replacing launch(Here) by thread, the compiler produces the following error:

Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function

That is because delay is a special suspending function that does not block a thread, but suspends coroutine and it can be only used from a coroutine.

Bridging blocking and non-blocking worlds

The first example mixes non-blocking delay(...) and blocking Thread.sleep(...) in the same code of main function. It is easy to get lost. Let's cleanly separate blocking and non-blocking worlds by using runBlocking { ... }:

fun main(args: Array<String>) = runBlocking { // start main coroutine
    launch(Here) { // create new coroutine without an explicit threading policy
        delay(1000L)
        println("World!")
    }
    println("Hello,") // main coroutine continues while child is delayed
    delay(2000L) // non-blocking delay for 2 seconds to keep JVM alive
}

You can get full code with imports here

The result is the same, but this code uses only non-blocking delay.

runBlocking { ... } works as an adaptor that is used here to start the top-level main coroutine. The regular code outside of runBlocking blocks, until the coroutine inside runBlocking is active.

This is also a way to write unit-tests for suspending functions:

class MyTest {
    @Test
    fun testMySuspendingFunction() = runBlocking {
        // here we can use suspending functions using any assertion style that we like
    }
}

Waiting for a job

Delaying for a time while a child coroutine is working is not a good approach. Let's explicitly wait (in a non-blocking way) until the other coroutine that we have launched is complete:

fun main(args: Array<String>) = runBlocking { 
    val job = launch(Here) { // create new coroutine and keep a reference to its Job
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join() // wait until child coroutine completes
}

You can get full code with imports here

Now the result is still the same, but the code of the main coroutine is not tied to the duration of the child coroutine in any way. Much better.

Extract function refactoring

Let's extract the block of code inside launch(Here} { ... } into a separate function. When you perform "Extract function" refactoring on this code you get a new function with suspend modifier. That is your first suspending function. Suspending functions can be used inside coroutines just like regular functions, but their additional feature is that they can, in turn, use other suspending functions, like delay in this example, to suspend execution of a coroutine.

fun main(args: Array<String>) = runBlocking {
    val job = launch(Here) { doWorld() }
    println("Hello,")
    job.join()
}

// this is your first suspending function
suspend fun doWorld() {
    delay(1000L)
    println("World!")
}

You can get full code with imports here

Coroutines ARE light-weight

Run the following code:

fun main(args: Array<String>) = runBlocking {
    val jobs = List(100_000) { // create a lot of coroutines and list their jobs
        launch(Here) {
            delay(1000L)
            print(".")
        }
    }
    jobs.forEach { it.join() } // wait for all jobs to complete
}

You can get full code with imports here

It starts 100K coroutines and, after a second, each coroutine prints a dot. Now, try that with threads. What would happen? (Most likely your code will produce some sort of out-of-memory error)