๐ŸŽ“ A1Learner
๐Ÿ”
0 of 28 lessons completed

Kotlin Structured Concurrency

Structured concurrency ties child coroutines to the scope that starts them. A scope does not finish until every child inside it has finished.

Children fill a shared list

Two children each add one letter to the list. The scope returns only after both adds, so the print after it sees the full list.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val log = mutableListOf<String>()
    coroutineScope {
        launch {
            log.add("a")
        }
        launch {
            log.add("b")
        }
    }
    log.add("after scope")
    println(log.joinToString())
}

A scope that returns a value

Two async children build the parts of a total. The awaits run in a fixed order, and their sum is the value of the scope block.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val total = coroutineScope {
        val first = async {
            delay(20)
            30
        }
        val second = async {
            delay(10)
            12
        }
        first.await() + second.await()
    }
    println(total)
}

The scope waits for every child

Each child adds one to the counter after its own delay. The counter reads 2 after the scope, because the scope waited for both children.

import kotlinx.coroutines.*

fun main() = runBlocking {
    var finished = 0
    coroutineScope {
        launch {
            delay(50)
            finished++
        }
        launch {
            delay(20)
            finished++
        }
    }
    println(finished)
}

A failing child ends the scope

The first child throws at once, so the scope ends and the delayed sibling never prints. The catch block around runBlocking prints only the message of the failure.

import kotlinx.coroutines.*

fun main() {
    try {
        runBlocking {
            coroutineScope {
                launch {
                    throw IllegalStateException("boom")
                }
                launch {
                    delay(100)
                    println("sibling finished")
                }
            }
            println("after scope")
        }
    } catch (e: Exception) {
        println("caught: " + e.message)
    }
}

Example

import kotlinx.coroutines.*

fun main() = runBlocking {
    val log = mutableListOf<String>()
    val label = coroutineScope {
        launch {
            log.add("started")
        }
        val name = async {
            delay(20)
            "report"
        }
        val count = async {
            delay(10)
            3
        }
        name.await() + " " + count.await()
    }
    println(log.joinToString())
    println(label)
}

Key points

  • coroutineScope starts a new scope for its block.
  • Children launched inside the scope belong to that scope.
  • The scope returns only after all of its children finish.
  • coroutineScope returns the value of the last expression in its block.
  • A child that fails ends the scope and stops the other children.
  • The failure reaches the code around runBlocking, where a try and catch block can read its message.

In this example, the launched child adds started to the log inside the scope. The two async children supply the name and the count, awaited in that order. The scope returns the joined label only after every child is done, so the log print is complete.

Note

Work collected inside the scope is complete as soon as the scope returns.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Kotlin Playground

Exercise

What does coroutineScope do before it returns?