Kotlin : Mastering Asynchronous Programming with Kotlin Coroutines: A Guide to async {}, await, and awaitAll

MaKB
4 min readAug 4, 2023

Introduction:

Asynchronous programming is a crucial aspect of modern software development, enabling applications to perform multiple tasks concurrently, such as fetching data from remote servers, performing I/O operations, and more, without blocking the main thread. Kotlin, being a powerful language for Android and backend development, provides excellent support for asynchronous programming through Kotlin Coroutines. In this blog post, we will dive into the concepts of async {}, await, and awaitAll, three essential functions in Kotlin Coroutines that make asynchronous programming a breeze.

Understanding Asynchronous Programming:

Before we explore async {}, await, and awaitAll, let's briefly understand what asynchronous programming is and why it is essential. Traditionally, when performing time-consuming operations like network requests or disk I/O, developers had to use callbacks or threads to prevent blocking the main thread. Asynchronous programming allows tasks to be executed independently, enabling applications to remain responsive and handle multiple operations concurrently without creating separate threads manually.

Introducing async {}:

In Kotlin Coroutines, async {} is a coroutine builder that starts a new coroutine, which represents an asynchronous computation. This function returns an instance of Deferred, which can be thought of as a "promise" of a result that will be available sometime in the future. The main advantage of using async {} is that it allows you to perform multiple asynchronous tasks concurrently, improving the overall efficiency of your code.

Deferred is a generic interface that represents a "promise" of a future result that will be available later after an asynchronous computation is complete. It is returned by the async {} coroutine builder and can be used to retrieve the result or handle exceptions once the computation is finished.

interface Deferred<out T> : Job {
val isCompleted: Boolean
val isCancelled: Boolean
val isActive: Boolean

suspend fun await(): T
}

Using await for Concurrent Processing:

When you have launched a single coroutine through async {}, you can use the await function to wait for its completion and retrieve the result. The await function is a suspending function that waits for the completion of the Deferred and returns the result.

import kotlinx.coroutines.*

fun main() = runBlocking {
// Start an asynchronous computation using async block
val deferredResult: Deferred<Pair<String, String>> = async {
fetchAsyncData("Server A")
}

// Other work can be done here concurrently

// Now, retrieve the result of the async computation using `await`
val result = deferredResult.await()
println("Received data: $result")
}

suspend fun fetchAsyncData(server: String): Pair<String, String> {
delay(2000)
val data = "Async data from $server"
return server to data
}
Output :

Received data: (Server A, Async data from Server A)

Using awaitAll for Concurrent Processing:

When you have multiple coroutines launched through async {}, you might want to wait for all of them to complete before moving forward with processing the results. This is where awaitAll comes in handy. It is a suspending function that waits for all the given Deferred instances to complete and returns a list of results in the same order as the input.

import kotlinx.coroutines.*

fun main() = runBlocking {
println("1. Requesting data from multiple servers")

// Start multiple asynchronous computations using async block
val deferredResults = listOf(
async { fetchAsyncData("Server A") },
async { fetchAsyncData("Server B") },
async { fetchAsyncData("Server C") }
)

// Do some other work here...

// Now, wait for the completion of all the async computations using awaitAll
val results = awaitAll(*deferredResults.toTypedArray())

results.forEachIndexed { index, result ->
println("${index + 2}. Received data from ${result.first}: ${result.second}")
}
}

suspend fun fetchAsyncData(server: String): Pair<String, String> {
delay(2000)
val data = "Async data from $server"
return server to data
}
Output :

1. Requesting data from multiple servers
2. Received data from Server A: Async data from Server A
3. Received data from Server B: Async data from Server B
4. Received data from Server C: Async data from Server C

Conclusion:

Kotlin Coroutines provide a powerful and concise way to handle asynchronous programming in Kotlin applications. By utilizing the async {} coroutine builder, you can start multiple asynchronous computations concurrently and retrieve the results using await. Combining it with the awaitAll function allows you to wait for all these tasks to complete efficiently. This approach not only makes your code more readable and maintainable but also improves the overall performance of your application.

Remember that proper error handling and cancellation are crucial in asynchronous programming to handle exceptional cases and prevent potential memory leaks. Kotlin Coroutines provide a variety of techniques to manage these aspects effectively.

By mastering async {}, await, and awaitAll, you will be better equipped to develop responsive, efficient, and highly scalable applications that harness the full potential of asynchronous programming in Kotlin. Happy coding! 🚀

Please follow to get updated posts and hit like to motivate me

Thanks 😊🙏

If this post was helpful, please click the clap 👏 button below a few times to show your support!

--

--

MaKB
MaKB

Written by MaKB

Experienced software engineer with demonstrated history of working in telecommunications industry along with many other sectors like education, e-commerce etc.

Responses (3)