π Why Coroutines?
π What are Coroutines?β
Coroutines are a core feature of Kotlin that makes asynchronous programming easy and intuitive. You can escape from complex callback hell and write code as if it were synchronous!
π‘ Problems with Traditional Asynchronous Programmingβ
Callback Hellβ
// β Callback style (hard to read and maintain)
fun fetchUser(userId: String, callback: (User?) -> Unit) {
fetchFromNetwork(userId) { result ->
if (result != null) {
fetchProfile(result.id) { profile ->
if (profile != null) {
fetchPosts(profile.id) { posts ->
callback(User(result, profile, posts))
}
} else {
callback(null)
}
}
} else {
callback(null)
}
}
}
Cost of Threadsβ
// β Threads are expensive
fun downloadFiles() {
repeat(1000) {
Thread {
// Each thread consumes a lot of memory
downloadFile("file_$it.txt")
}.start()
}
// Risk of running out of memory!
}
β¨ Advantages of Coroutinesβ
1. Concise Codeβ
// β
Coroutines (easy to read and intuitive)
suspend fun fetchUser(userId: String): User? {
val result = fetchFromNetwork(userId) ?: return null
val profile = fetchProfile(result.id) ?: return null
val posts = fetchPosts(profile.id)
return User(result, profile, posts)
}
2. Lightweight Concurrencyβ
// β
Thousands of coroutines are no problem
suspend fun downloadFiles() {
coroutineScope {
repeat(10000) {
launch {
// Lightweight coroutines!
downloadFile("file_$it.txt")
}
}
}
}
3. Structured Concurrencyβ
// β
Automatic lifecycle management
suspend fun processData() {
coroutineScope {
val job1 = launch { task1() }
val job2 = launch { task2() }
// Automatically completes when all tasks finish
}
}
π― Real-World Usage Examplesβ
Network Requestsβ
// Clean without callbacks!
suspend fun loadUserData(userId: String): UserData {
val user = apiService.getUser(userId)
val posts = apiService.getPosts(userId)
val friends = apiService.getFriends(userId)
return UserData(user, posts, friends)
}
UI Updatesβ
// Without blocking the main thread
suspend fun updateUI() {
val data = withContext(Dispatchers.IO) {
// Load data in the background
loadDataFromDatabase()
}
// Update UI on the main thread
updateViews(data)
}
Parallel Processingβ
// Multiple tasks simultaneously!
suspend fun fetchAllData(): CombinedData = coroutineScope {
val users = async { fetchUsers() }
val products = async { fetchProducts() }
val orders = async { fetchOrders() }
CombinedData(
users.await(),
products.await(),
orders.await()
)
}
π₯ Coroutines vs Threadsβ
| Feature | Coroutines | Threads |
|---|---|---|
| Weight | Very lightweight | Heavy |
| Creation Cost | Almost none | Expensive |
| Quantity | Tens of thousands possible | Thousands limit |
| Context Switch Cost | Low | High |
| Cancellation | Easy | Difficult |
Performance Comparisonβ
// Coroutines: Running 100,000
suspend fun testCoroutines() {
val time = measureTimeMillis {
coroutineScope {
repeat(100_000) {
launch {
delay(1000)
}
}
}
}
println("Coroutines: ${time}ms") // ~1000ms
}
// Threads: Even with just 10,000...
fun testThreads() {
val time = measureTimeMillis {
repeat(10_000) {
Thread {
Thread.sleep(1000)
}.start()
}
}
println("Threads: ${time}ms") // May run out of memory!
}
π¨ Core Concepts of Coroutinesβ
Suspend (Suspension)β
// suspend functions can only be called within a coroutine
suspend fun doWork() {
delay(1000) // Wait without blocking the thread
println("Work completed")
}
Structured Concurrencyβ
// Lifecycle management through parent-child relationships
fun main() = runBlocking {
launch { // Parent
launch { // Child 1
delay(1000)
println("Child 1 completed")
}
launch { // Child 2
delay(2000)
println("Child 2 completed")
}
println("Parent waits until children finish")
}
}
π€ Frequently Asked Questionsβ
Q1. Are coroutines new threads?β
A: No! Coroutines are lightweight work units that run on top of threads.
// Multiple coroutines can run on a single thread
fun main() = runBlocking {
repeat(3) { i ->
launch {
println("Coroutine $i: ${Thread.currentThread().name}")
}
}
}
// They can all run on the same thread!
Q2. Do I need to make all functions suspend?β
A: No! Only make functions that need asynchronous operations suspend.
// β Not necessary
suspend fun add(a: Int, b: Int) = a + b
// β
Necessary
suspend fun fetchData() = withContext(Dispatchers.IO) {
loadFromNetwork()
}
Q3. When should I use coroutines?β
A: Use them in these cases!
// β
Network requests
suspend fun loadData() = api.fetchData()
// β
Database operations
suspend fun saveUser(user: User) = db.insert(user)
// β
File I/O
suspend fun readFile(path: String) = withContext(Dispatchers.IO) {
File(path).readText()
}
// β
Heavy computations
suspend fun heavyComputation() = withContext(Dispatchers.Default) {
// Complex calculations
}
π¬ Conclusionβ
Make asynchronous programming easy with coroutines!
Key Takeaways:
β
Escape callback hell
β
Lightweight concurrency
β
Intuitive code
β
Structured lifecycle
β
Much more efficient than threads
Next Step: Try using coroutines in practice with Coroutines Basics!