DEV Community

Cover image for Asynchronous Programming in Kotlin: What is it, and why should you use it?
Alan Gomes for Comunidade Dev Space

Posted on

Asynchronous Programming in Kotlin: What is it, and why should you use it?

1 – Introduction
Imagine using a social media app where you have to wait for the feed to load completely before you can send a message. Or a banking system that crashes while processing a transaction. These situations would be frustrating, right?

These problems are solved with asynchronous programming, which allows us to execute multiple tasks at the same time, efficiently and without blocking.

But what is asynchronous programming?
Asynchronous programming is the execution of code that performs tasks without blocking the main flow. While a long-running task is executing, other tasks can continue without interruption.

To understand this better, we need to talk about threads.

  • What are threads?
    Threads are execution paths used by the operating system to execute tasks. In a typical program, everything happens in the main thread. If a task takes too long in this thread , the entire program can crash.

  • What if we use multiple threads?
    Using multiple threads allows you to execute tasks in parallel, but it also brings challenges:

    • High creation cost.
    • Excessive consumption of resources.
    • Difficulty in management (deadlocks, for example).
  • Practical example
    Think about making coffee. Imagine one person doing all the work alone (synchronous) versus two people sharing the tasks (asynchronous). The second scenario is more efficient, and the same is true for asynchronous programming.

2 – Problem with Blocking Tasks
The biggest problem with blocking tasks is that they halt program execution until they complete.

Example of blocking in the main thread :

fun main ( ) {
    println ( "Loading data...")
    Thread.sleep (3000) // Simulates a long task that blocks execution
    println ( "Data loaded!")
}
Enter fullscreen mode Exit fullscreen mode

Console output:

Loading data...
(3 seconds lock)
Data loaded!

While the program is in the sleep state , it cannot execute anything else. Its main function is completely blocked.

While multiple threads can resolve blocking, managing them manually is complex and error-prone. This includes:

  • Deadlocks (threads waiting indefinitely)
  • Excessive CPU and memory consumption
  • Difficulty synchronizing tasks

3 - The modern solution: Kotlin coroutines **
Coroutines are modern abstractions for asynchronous programming. They allow you to execute concurrent tasks (parallel or not) without blocking the current thread.

How do they work?* *
Think of coroutines as **multitasking assistants
. They take work from multiple people (threads) and manage it intelligently so that no one gets overwhelmed.

Main advantages of coroutines :

  • Concurrent tasks: Perform multiple operations, such as API requests, without blocking.
  • Non-blocking suspension: You can pause a coroutine ( suspend ) without preventing other tasks from continuing.
  • Automatic lifecycle management: Use scopes to associate coroutines with the lifecycle of components, such as ViewModels in Android.
  • Native support in Kotlin : Coroutines are integrated into the language, making their adoption easier.

4 – Practical Example: Synchronous Coffee vs. Asynchronous Coffee

Synchronous Coffee :* *

import kotlinx.coroutines.*

fun main() = runBlocking {
    // runBlocking : Starts a "blocking" coroutine .
// It blocks the main thread while executing coroutines inside it.
    makeSynchronousCafe()
    println("Coffee finished.")
}

suspend fun makeSynchronousCafe() {
// suspend : Allows the function to be "suspended" without blocking the thread.
// These functions can only be called inside coroutines or other ' suspend ' functions .
    println("Getting water...")
    delay(1000) // delay: Suspends execution for 1 second without blocking the thread.
    println("Boiling water...")
    delay(2000) // Simulates a long task, like boiling water.
    println("Making coffee...")
    delay(1000) // Another 1 second wait, simulating the process of making coffee.
}
Enter fullscreen mode Exit fullscreen mode

Console output:

Getting the water...
Boiling the water...
Passing the coffee...
Coffee finished.

  • Problem: All tasks depend on the previous one. Total time is 4 seconds.

Asynchronous Coffee:

import kotlinx.coroutines.*

fun main() = runBlocking {
// runBlocking : Blocks the main thread while execute coroutines .
    // Here it is used to ensure that all tasks are completed before terminating the program.

    val water = async { boilWater() }
// async : Starts a coroutine to execute a task and returns a " Deferred ".
// The " Deferred " is like a promise that the task will return a result in the future.

    val filter = async { prepareFilter() }
// Another coroutine is started to prepare the filter, in parallel with ' boilWater '.

    water.await()
// await : Suspends execution until the result of the associated coroutine ( water ) is ready.

    filter.await()
// Wait for the completion of the coroutine that prepares the filter.

    println("Making coffee...")
    delay(1000) // delay: Suspends execution for 1 second without blocking the thread.
    println("Coffee finished.")
}

suspend fun boilWater() {
// suspend : Allows this function to be used in coroutines .
    println("Getting water...")
    delay(1000) // Suspends execution for 1 second.
    println("Boiling water...")
    delay(2000) // Simulates the time needed to boil water.
}

suspend fun prepareFilter() {
    println("Preparing the filter and powder...")
    delay(1500) // Simulates the time needed to prepare the filter.
}
Enter fullscreen mode Exit fullscreen mode

Console output:

Getting the water...
Preparing the filter and powder...
Boiling water...
Making coffee...
Coffee finished.

  • Benefit: Tasks are done in parallel. Total time is reduced to 3 seconds.

5 – Conclusion
As we have seen throughout this article, asynchronous programming is an essential approach to solving blocking problems and making better use of resources.

Key learnings:

  • Threads have important limitations, but Kotlin coroutines offer an efficient and modern solution.
  • Using coroutines allows you to write simpler, more readable and scalable code.

In the next article, we will explore how to use ** Dispatchers and Contexts** to manage threads and optimize task execution with coroutines .

Reference
Official Kotlin documentation on coroutines

Top comments (0)