Last Updated on July 2, 2024

In this article we are exploring what Kotlin coroutines are and how Kotlin coroutines work.

Kotlin coroutines are a way to achieve asynchronous programming in a synchronous fashion.

There are ways to achieve asynchronous programming with callbacks and reactive programming. But implementing callback is counter intuitive and introduces complexity known as callback hell. 

Reactive libraries are no better. For example RxJava introduces complex code that is difficult to understand and maintain.

Synchronous fashion is important for simplifying code complexity and making debugging easier.

Advantage of coroutines is that they are easy to implement and debug.

How Kotlin coroutines work

They achieve their magic through a mechanism called continuations

The Kotlin compiler will rewrite every suspending function adding a parameter of type Continuation to the function signature.

suspend func foo(){...}

to

fun foo(continuation: Continuation<*>): Any

A continuation captures the current state of a coroutine, including its local variables and the point where it was paused. 

Components controlling coroutine execution are Dispatchers.

When the awaited operation (like a network call) finishes, the dispatcher retrieves the saved continuation. It then uses this continuation to resume the coroutine from the exact point where it was suspended.

This allows the coroutine to be suspended and resumed later without losing its context.

Kotlin coroutines promote structured concurrency, meaning the lifetime of child coroutines is managed by their parent scope. This helps prevent coroutine leaks and ensures proper cleanup of resources.

The way of implementing the structural concurrency in Kotlin are coroutine scopes.

suspend fun <R> coroutineScope(
  block: suspend CoroutineScope.() -> R
): R

Execution of the block section is suspended(blocked) until all the coroutines inside are finished.

Builders are functions that provide a high-level abstraction for creating and managing coroutines. Coroutine builders are a crucial part of how Kotlin achieves asynchronous programming.

Let’s see how this is implemented and functions in practice.

Kotlin Coroutines Examples

Suspend Keyword

When a coroutine calls a suspend function, it essentially yields control back to the dispatcher. The dispatcher then saves the coroutine’s continuation and switches to another coroutine that’s ready to run.

import kotlinx.coroutines.*
suspend fun main() {
   println("start main function")
   println("thread: ${Thread.currentThread().name}")
   foo();
   println("end main function")
}
suspend fun foo() {
   delay(1000L)
   println("foo function..")
   println("thread: ${Thread.currentThread().name}")
}

The output of above program:

We see sequential execution and foo function execution started in the main thread. But after the suspension its executed in different thread (main and DefaultExecutor)

Coroutine Builders

Builders provide a higher-level abstraction for managing asynchronous workflows.

Builders are functions helping create coroutines explicitly and start their execution.

There are three main  builder functions: launch, async, and runBlocking.

Launch Builder

The signature of launch function:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job

To create a coroutine, we also need a CoroutineContext and a lambda with the code to execute.

Here is an example:

import kotlinx.coroutines.*

suspend fun main() {
    coroutineScope {
        launch {
            foo();
        }
        launch {
            bar();
        }

    }
    println("Hello from Main Thread!")
    //Thread.sleep(2000L)
}

suspend fun foo() {
    println("start foo thread: ${Thread.currentThread().name}")
    delay(500L)
    println("foo function..")
    println("end foo thread: ${Thread.currentThread().name}")
}
suspend fun bar() {
    println("start bar thread: ${Thread.currentThread().name}")
    delay(1000L)
    println("bar function..")
    println("end bar thread: ${Thread.currentThread().name}")
}

The output is:

We can see that the two functions execute concurrenctly and the second function.

Async Builder

Async builder function is a way of creating coroutine functions returning a value.

Example:

import kotlinx.coroutines.*
suspend fun main() {
    println("start main")
    returningFunction()
    println("end main")
}
suspend fun returningFunction() {
    coroutineScope {
        val foo: Deferred<String> = async {
            fooReturnValue()
        }
        val bar: Deferred<String> = async {
            barReturnValue()
        }

        println("retuned: ${foo.await()} and ${bar.await()}")
    }
}

suspend fun fooReturnValue(): String {
    println("start foo thread: ${Thread.currentThread().name}")
    delay(500L)
    println("end foo thread: ${Thread.currentThread().name}")
    return "Foo"
}
suspend fun barReturnValue(): String {
    println("start bar thread: ${Thread.currentThread().name}")
    delay(1000L)
    println("end bar thread: ${Thread.currentThread().name}")
    return "Bar"
}

The output:

The execution of the two coroutines is still concurrent. The main function awaits the completion of the two coroutines to print the final message.

RunBlocking Builder

It is another way of calling coroutines in main function without suspend keyword.

Example:

import kotlinx.coroutines.*

fun main() = runBlocking {
foo()
bar()
}
suspend fun foo() {
println("start foo thread: ${Thread.currentThread().name}")
delay(500L)
println("foo function..")
println("end foo thread: ${Thread.currentThread().name}")
}

suspend fun bar() {
println("start bar thread: ${Thread.currentThread().name}")
delay(1000L)
println("bar function..")
println("end bar thread: ${Thread.currentThread().name}")
}

Output:

We see the two functions executed sequentially in the main thread, coroutine scope.

Conclusion

We saw the importance of coroutines, basic concepts helping implement them, and made a simplified explanation of how they’re implemented under the hood.

By leveraging coroutines and their builders, you can write clean, concise, and asynchronous code that keeps your application responsive and efficient.

With their lightweight nature and structured approach to concurrency, Kotlin coroutines are a valuable tool for modern Kotlin developers.

Scroll to Top