Mastering async and await in Kotlin Coroutines
What Are async
and await
?
In Kotlin, the async
function is used to launch a coroutine that performs some asynchronous task. It returns a Deferred
result, which can be thought of as a future result that will be available at some point. To get this result, you use the await
function, which suspends the current coroutine until the result is ready.
async
: Starts a coroutine and returns aDeferred
object, which represents a value that will be available later.await
: Waits for theDeferred
result to be available and retrieves it.
async
and await
are perfect when you need to perform multiple tasks concurrently and wait for their results.
Basic Example of async
and await
Let’s see a simple example where we perform two tasks concurrently using async
and await
.
import kotlinx.coroutines.*
fun main() = runBlocking {
val result1 = async {
performTask1()
}
val result2 = async {
performTask2()
}
// Use await() to wait for both results
println("Result 1: ${result1.await()}")
println("Result 2: ${result2.await()}")
}
suspend fun performTask1(): Int {
delay(1000L) // Simulating a long-running task
return 10
}
suspend fun performTask2(): Int {
delay(2000L) // Simulating another long-running task
return 20
}
Explanation:
Two tasks are performed asynchronously using
async
.The
await()
function waits for the result of both tasks and prints the values once they're ready.The tasks run concurrently, meaning they don’t wait for each other to complete before starting.
Benefits of async
and await
Concurrency: Tasks run in parallel, improving performance, especially when dealing with I/O or network-bound tasks.
Easy Result Handling: The
Deferred
object allows you to access the result once it’s available.Structured Concurrency: Both
async
andawait
work well within structured concurrency, meaning coroutines are properly managed and cancelled when no longer needed.
Example: Running Multiple Tasks Concurrently
Now, let’s say we have three tasks that take varying amounts of time, and we want to run them concurrently. async
is the ideal tool to achieve this:
import kotlinx.coroutines.*
fun main() = runBlocking {
val task1 = async { performTask("Task 1", 1000L) }
val task2 = async { performTask("Task 2", 2000L) }
val task3 = async { performTask("Task 3", 1500L) }
println("Waiting for tasks to finish...")
// Waiting for all tasks to complete
println("${task1.await()} completed")
println("${task2.await()} completed")
println("${task3.await()} completed")
}
suspend fun performTask(taskName: String, time: Long): String {
delay(time)
return taskName
}
Explanation:
Three tasks are launched concurrently using
async
.We
await()
each task, meaning the program will wait for each task to complete and print when they are done.Even though the tasks have different delays, they run concurrently, making the total runtime much shorter than if they ran sequentially.
Example: Async with Exception Handling
You can use async
with try-catch
blocks to handle exceptions that occur in one or more of the concurrent coroutines.
import kotlinx.coroutines.*
fun main() = runBlocking {
val task = async {
try {
riskyTask()
} catch (e: Exception) {
println("Caught exception: ${e.message}")
"Error"
}
}
println("Task result: ${task.await()}")
}
suspend fun riskyTask(): String {
delay(1000L)
throw IllegalArgumentException("Something went wrong!")
}
Explanation:
We use a
try-catch
block insideasync
to handle any potential exceptions.The
riskyTask()
throws an exception, but it’s caught, and a fallback result ("Error") is returned instead of crashing the program.
When to Use async
vs launch
You might wonder when to use async
and launch
. Here's the difference:
launch
: Used for coroutines that don't return a result. It’s mainly used when you don’t need to wait for the coroutine to finish or return a value.async
: Used for coroutines that return a result. It allows you to wait for the result usingawait()
, making it perfect for concurrent operations where results are needed.
In short:
Use
launch
if you’re not interested in the result of a coroutine.Use
async
if you need a result from a coroutine and want to perform concurrent operations.
Example: Sequential Execution with async
vs Concurrent Execution
Let’s compare how tasks are executed sequentially versus concurrently using async
.
Sequential Execution (Without async
):
import kotlinx.coroutines.*
fun main() = runBlocking {
val result1 = performTask1()
val result2 = performTask2()
println("Result 1: $result1")
println("Result 2: $result2")
}
suspend fun performTask1(): Int {
delay(1000L)
return 10
}
suspend fun performTask2(): Int {
delay(2000L)
return 20
}
- In this case,
performTask2()
starts only afterperformTask1()
finishes, making the total execution time 3 seconds.
Concurrent Execution (With async
):
import kotlinx.coroutines.*
fun main() = runBlocking {
val result1 = async { performTask1() }
val result2 = async { performTask2() }
println("Result 1: ${result1.await()}")
println("Result 2: ${result2.await()}")
}
- Here, both tasks are run concurrently, making the total execution time 2 seconds (the longest task).
Summary of Key Points
async
launches a coroutine that returns a result, represented by aDeferred
object.await
suspends the coroutine until the result is available.async
andawait
are perfect for running multiple tasks concurrently and waiting for their results.They simplify concurrent programming and can handle exceptions within coroutines.
Use
async
when you need to retrieve the result of a coroutine, and uselaunch
when you don't need a return value.
Conclusion
Using async
and await
in Kotlin coroutines makes writing concurrent code simple and efficient. Whether you need to run tasks in parallel, wait for results, or handle errors gracefully, async
and await
provide a clean solution. Feel free to experiment with the examples provided to deepen your understanding.
Subscribe to my newsletter
Read articles from Mukesh Rajput directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Mukesh Rajput
Mukesh Rajput
Specializing in creating scalable and maintainable applications using MVVM and Clean Architecture principles. With expertise in Ktor, Retrofit, RxJava, View Binding, Data Binding, Hilt, Koin, Coroutines, Room, Realm, and Firebase, I am committed to delivering high-quality mobile solutions that provide seamless user experiences. I thrive on new challenges and am constantly seeking opportunities to innovate in the Android development space.