Coroutines has been a core part of Kotlin for a while now. And more Android libraries are adopting coroutines to handle their internal asnychronous communication. For various use cases, Using Coroutines is defining a suspendible function and executing from a scope. But what is a Coroutine? I wanted to explore this topic in this post.

Coroutines are a suspendible unit of computation. The term suspendible comes from the operating system concept non-preemprive multitasking or Coorperative multitasking. The key takeaway is that system does not initiate a context switch and processes voluntarily give up control upon certain conditions.

Coroutines provide a framework for asynchronous programming. In asynchronous programming, main goal is to not block the primary execution flow and handle occurrence of external and internal events accordingly. In that regard, Coroutines in Kotlin follows a structured concurrency principle. This ensures that there is no memory leak and no process lost during a concurrent computation.

We can dissect the coroutine execution into 3 parts to understand how the structured concurrency is implemented. These are:

  • Scope
  • Context
  • Coroutine

Scopes

Scopes are suspendible code blocks which coroutines can be executed. They are obtained using a Corotuine Builder. There is one special CoroutineBuilder called RunBlocking. It can block the thread it is called until all the computation is finished. So it is mainly used in the main function block or tests.

import kotlinx.coroutines.*

fun main() = runBlocking { // This creates a Coroutine Scope
 launch { printWorld() }
 println("Hello")
}

suspend fun printWorld() { // suspendible computation
 delay(3000L)
 println("World")
}

CoroutineScope is another building block to create a scope. The main difference is that it does not block the thread and releases the underlying resources for other tasks.

Context

Context is the set of elements stored in the scope. These elements includes the Job of the coroutine and its Dispatcher type. Any coroutine computation defined with a scope can be considered as a Job. When we structure different coroutines using a child-parent hierarchy, the parent context acquires the children coroutines as a job handle and children coroutine, if not defined otherwise, acquires the parent context. This ensures the structured concurrency.

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    // launch a coroutine to process some kind of incoming request
    val request = launch {
        // it spawns two other jobs
        launch(Job()) {
            println("job1: I run in my own Job and execute independently!")
            delay(1000)
            println("job1: I am not affected by cancellation of the request")
        }
        // and the other inherits the parent context
        launch {
            delay(100)
            println("job2: I am a child of the request coroutine")
            delay(1000)
            println("job2: I will not execute this line if my parent request is cancelled")
        }
    }
    delay(500)
    request.cancel() // cancel processing of the request
    delay(1000) // delay a second to see what happens
    println("main: Who has survived request cancellation?")
}

This code snippet shows that we can compose suspendible functions by nesting it with coroutine builders and acquiring context enables structured control for cancellation and completion.

Coroutine

Suspend modifier adds an additional constraint and it can’t be called within normal functions. Suspendable functions can call other suspendable functions and they can be used to suspend the execution of a coroutine if needed.

import kotlinx.coroutines.*

fun main() {
    runBlocking {
        doWork() // OK
        launch {
            doWork() // OK
            doMoreWork() // OK
        }
 }
    doWork() // this line gives error.
}

suspend fun doWork() {
 delay(1000L)
 println("is Work Done?")
}

suspend fun doMoreWork() {
 delay(1000L)
 doWork()
 println("More work Done?")
}

These concepts are just the tip of the iceberg, but they are the necessary fundamentals to use the coroutines. You can find more information at Official Kotlin Guides.