Beginner: Kotlin · Coroutines and Flow · topic 15 of 50 · 30 min
launch, async and structured concurrency
Explanation
Scopes: where coroutines live
A coroutine is always started from a CoroutineScope, an object that tracks the coroutines started in it. When the scope ends or is cancelled, all its coroutines are cancelled too. This rule, called structured concurrency, prevents work from running forever in the background after a screen is closed, which wastes battery and data and can crash the app.
| Scope | Where | Cancelled when |
|---|---|---|
| viewModelScope | Inside a ViewModel | The ViewModel is cleared (the screen is gone for good) |
| lifecycleScope | Inside an Activity or Fragment | The lifecycle is destroyed |
| coroutineScope { } | Inside a suspend function | It finishes when all its children finish |
| runBlocking | main and tests only | Its block finishes (it blocks the thread) |
launch: fire and forget
import kotlinx.coroutines.*
fun main() = runBlocking {
val job: Job = launch {
delay(500)
println("Receipt emailed")
}
println("Payment done")
job.join() // wait for the job to finish (optional)
}async: get a result
suspend fun price(): Int { delay(1000); return 499 }
suspend fun stock(): Int { delay(1000); return 12 }
fun main() = runBlocking {
val p = async { price() } // both start now
val s = async { stock() }
println("Rs ${p.await()}, ${s.await()} left") // about 1 second in total, not 2
}Use launch when you do not need a result (save, send, log). Use async when you need a value, and especially to run several independent calls at the same time.
Structured concurrency in practice
suspend fun loadDashboard(): String = coroutineScope {
val user = async { delay(300); "Asha" }
val orders = async { delay(500); 7 }
"${user.await()} has ${orders.await()} orders"
} // returns only when both children are done; if one fails, the other is cancelledCancellation
Cancelling a scope or Job asks its coroutines to stop. Cancellation is cooperative: a coroutine notices it at suspension points like delay, or when it checks isActive. Long CPU loops should call ensureActive() or yield() now and then. Library suspend functions (Retrofit, Room) already cooperate.
fun main() = runBlocking {
val job = launch {
repeat(100) { i ->
println("Uploading part $i")
delay(100)
}
}
delay(350)
job.cancel() // user pressed Cancel
println("Upload cancelled")
}Errors
An exception inside launch cancels the parent scope by default and is reported as an uncaught error. Inside async, the exception is thrown when you call await(). The simplest rule: wrap the risky call in try/catch inside the coroutine and turn failures into an error state for the UI.
viewModelScope.launch {
try {
val data = repository.load()
_state.value = UiState.Success(data)
} catch (e: IOException) {
_state.value = UiState.Error("No internet connection")
}
}Common mistakes
- Using GlobalScope: its coroutines are not tied to any screen and can leak. Use viewModelScope or lifecycleScope.
- Awaiting async calls one by one right after starting each (async { a() }.await()), which removes the speed-up. Start all, then await.
- Catching Exception broadly and swallowing CancellationException, which breaks cancellation.
- Using runBlocking in Android UI code: it freezes the main thread.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
launch, async and structured concurrency · 1/7
Every coroutine is started inside a scope. When the scope ends, its coroutines are cancelled. This is structured concurrency.
Prefer a video by a teacher? Find free videos on “kotlin coroutines launch async await structured concurrency viewModelScope” on YouTube ↗
In real life: A home screen that loads three things
A shopping app's home screen needs the user profile, today's offers and recently viewed items, each from a different API taking about one second. Loading them one after another takes three seconds. The ViewModel starts three async calls inside viewModelScope and awaits them together, so the screen fills in about one second. If the user leaves the screen, viewModelScope cancels all three requests automatically.
import kotlinx.coroutines.*
suspend fun profile(): String { delay(1000); return "Rohit" }
suspend fun offers(): Int { delay(1000); return 5 }
suspend fun recent(): Int { delay(1000); return 3 }
fun main() = runBlocking {
val start = System.currentTimeMillis()
val a = async { profile() }
val b = async { offers() }
val c = async { recent() }
println("${a.await()}: ${b.await()} offers, ${c.await()} recent")
println("took about ${(System.currentTimeMillis() - start) / 1000} s") // about 1 s
}Practice (0/3 done)
Two independent suspend calls each take 1 second. With async for both, then await for both, about how long does it take?
Which scope should a ViewModel use to start coroutines?
Write suspend fun downloadAll(urls: List<String>): List<Int> that uses coroutineScope and async to "download" every URL at once (delay a random 200-800 ms and return the URL length). In main, call it with 5 URLs and print the results and total time. Then add a launch that cancels a long repeat loop after 1 second.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Dispatchers and withContextCoroutines and Flow · same module
- Flow and StateFlowCoroutines and Flow · same module
- Interview questions: KotlinAndroid career path
- async and await: waiting without blockingC# and .NET · Exceptions, delegates, events and async
- async and awaitJavaScript Programming (language in depth) · Asynchronous JavaScript
- Concurrency 2: atomics, async, futures and condition variablesC++ Programming (modern C++ and DSA) · Exceptions, concurrency and building projects with CMake
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What does launch return?
Answer: A Job
A Job lets you join or cancel the coroutine.
What does async return?
Answer: A Deferred<T> whose await() gives the value
Deferred is a Job with a result.
Why avoid GlobalScope in apps?
Answer: Its coroutines are not tied to a screen and can leak
Structured scopes cancel work automatically.
Where does an exception inside async surface?
Answer: When you call await()
await rethrows the exception.
Which is the best description of a coroutine?Related
Answer: A lightweight task that can suspend without blocking a thread
Coroutines run on threads but are much lighter.
What kind of work benefits most from async/await?Related
Answer: Waiting for network, disk or database (I/O)
async frees threads during I/O waits.
async function f() { return 3; } What is f()?Related
Which is the correct way to wait inside a coroutine?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Coroutines and Flow” before its exam: Dispatchers and withContext, Flow and StateFlow.
- Module 4 exam: Coroutines and Flow · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Android Studio and your first app. 50 topics left in the course before the final exam.