Kotlin coroutines are a powerful feature that simplify asynchronous programming. They allow developers to write asynchronous code in a sequential manner, making it easier to read and maintain. At the heart of coroutines are coroutine builders, specifically launch
and async
. These builders define how coroutines are started and managed.
In this blog, we’ll dive deep into the differences between launch
and async
, when to use them, and best practices for effective coroutine usage.
Understanding Coroutine Builders
A coroutine builder is a function that creates and starts a coroutine. The two primary coroutine builders in Kotlin are:
launch
: Used for fire-and-forget operations (does not return a result).async
: Used for parallel computations that return a result.
Let’s explore both in detail.
launch
– Fire and Forget
The launch
builder is used when you don’t need a result. It starts a coroutine that runs independently, meaning it does not return a value. However, it returns a Job
object, which can be used to manage its lifecycle (e.g., cancellation or waiting for completion).
Key Points:
- Does not return a result.
- Returns a
Job
that can be used for cancellation or waiting. - Runs in the background without blocking the main thread.
import kotlinx.coroutines.*
fun main() = runBlocking {
val job =
launch(Dispatchers.IO) {
val data = fetchData()
println("Data: $data")
}
job.join() // Ensures the coroutine completes before proceeding
println("Coroutine completed")
}
suspend fun fetchData(): String {
delay(1000) // Simulating network call
return "Fetched Data"
}
- The coroutine is launched using
launch(Dispatchers.IO) { ... }
. - The
fetchData()
function runs asynchronously without blocking the main thread. job.join()
ensures the coroutine completes before proceeding.
Output:
Data: Fetched Data
Coroutine completed
Note: If
job.join()
is not called, the program might exit before the coroutine completes, depending on the coroutine scope.
async
– Returns a Result
The async
builder is used when you need a result. It returns a Deferred<T>
object, which represents a future result. To retrieve the result, you must call await()
.
Key Points:
- Returns a
Deferred<T>
object, which must be awaited. - Used for parallel computations.
- Must be called within a coroutine scope.
import kotlinx.coroutines.*
fun main() = runBlocking {
val result = async(Dispatchers.IO) { fetchData() }
println("Data: ${result.await()}") // Await to get the result
}
suspend fun fetchData(): String {
delay(1000) // Simulating network call
return "Fetched Data"
}
async(Dispatchers.IO) { fetchData() }
starts an asynchronous task.- It returns a
Deferred<String>
object. - Calling
.await()
retrieves the result.
Output:
Data: Fetched Data
Important: If you do not call
await()
, the coroutine will run, but the result won’t be retrieved.
async
for Parallel Execution
One of the most powerful use cases for async
is running multiple tasks in parallel.
Running Two Tasks in Parallel
import kotlinx.coroutines.*
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val deferred1 = async { fetchData1() }
val deferred2 = async { fetchData2() }
val result1 = deferred1.await() // Waits for fetchData1
val result2 = deferred2.await() // Waits for fetchData2
val endTime = System.currentTimeMillis()
println("Results: $result1, $result2")
println("Total time: ${endTime - startTime} ms")
}
suspend fun fetchData1(): String {
delay(1000) // Simulating API call
return "Data 1"
}
suspend fun fetchData2(): String {
delay(1000) // Simulating API call
return "Data 2"
}
Here,
async { fetchData1() }
andasync { fetchData2() }
start in parallel.- Each coroutine takes 1 second.
- Since they run concurrently, the total execution time is ~1 second instead of 2 seconds (if they run sequentially then 2 seconds).
Best Practices for launch
and async
- Use
launch
when you don’t need a result (e.g., updating UI, logging, background tasks). - Use
async
when you need a result and always callawait()
. - For multiple async tasks, start them first, then call
await()
to maximize concurrency. - Avoid using
async
outside of structured concurrency unless you explicitly manage its lifecycle, as it can lead to untracked execution, potential memory leaks, or uncaught exceptions.
Conclusion
Kotlin’s launch
and async
coroutine builders serve distinct purposes:
- Use
launch
when you don’t need a result (fire-and-forget). - Use
async
when you need a result (and always callawait()
).
By understanding the differences and best practices, you can write efficient, safe, and scalable Kotlin applications using coroutines.