launch / async
Kotlin coroutines start with launch — fire-and-forget within a CoroutineScope. Combine with async for deferred results, withContext for dispatcher hops, supervisorScope for fault isolation, and viewModelScope/lifecycleScope for Android lifecycle integration.
launch, async, dispatchers, scope
EXAMPLE
// 1) Install
// implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0'
// implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0' // for Android
import kotlinx.coroutines.*
// 2) launch — fire and forget
fun main() = runBlocking {
val job = launch {
delay(500)
println("hello after 500ms")
}
job.join() // wait until the launched coroutine ends
}
// runBlocking is a TEST/MAIN bridge; in real apps use a proper scope.
// 3) launch returns a Job — you can wait, cancel, check state
val job: Job = scope.launch { /* … */ }
job.cancel()
job.isActive
job.isCancelled
job.isCompleted
job.invokeOnCompletion { cause -> /* … */ }
// 4) async — returns Deferred<T> (a Job with a value)
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
val deferred: Deferred<User> = scope.async { fetchUser(42) }
val user: User = deferred.await()
// async + await pattern for parallel work
fun loadProfile() = runBlocking {
val user = async { fetchUser(1) }
val posts = async { fetchPosts(1) }
println("${user.await()} ${posts.await().size}")
}
// 5) Dispatchers — control which thread pool
launch(Dispatchers.IO) { /* I/O */ }
launch(Dispatchers.Default) { /* CPU-bound */ }
launch(Dispatchers.Main) { /* UI (Android, JavaFX) */ }
launch(Dispatchers.Unconfined) { /* tests, advanced */ }
// Switch mid-coroutine with withContext
suspend fun fetchAndDisplay() {
val data = withContext(Dispatchers.IO) { api.fetch() } // I/O
withContext(Dispatchers.Main) { textView.text = data } // back to UI
}
// 6) Scope = lifecycle + cancellation
// CoroutineScope ties launched coroutines together; cancelling the scope cancels all children.
class Repository {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
fun refresh() {
scope.launch { /* fetch + cache */ }
}
fun shutdown() { scope.cancel() }
}
// Android — use viewModelScope / lifecycleScope so cancellation matches UI lifecycle.
class MyViewModel : ViewModel() {
fun load() = viewModelScope.launch {
val users = withContext(Dispatchers.IO) { api.fetchUsers() }
_users.value = users
}
}
// 7) supervisorScope — children fail independently
suspend fun loadDashboard() = supervisorScope {
val a = async { fetchA() }
val b = async { fetchB() }
val c = async { fetchC() }
listOf(a, b, c).map { runCatching { it.await() } } // failures don't cancel siblings
}
// Vs coroutineScope: any failure cancels all children + propagates.
// 8) Exceptions
// • Exception in a launched coroutine → propagated to parent Job → cancels scope (unless Supervisor)
// • CoroutineExceptionHandler catches unhandled in launch
val handler = CoroutineExceptionHandler { _, e -> log.error(e, "unhandled") }
scope.launch(handler) {
throw RuntimeException("oops")
}
// async exceptions surface at await(); wrap in try/catch.
// 9) Cancellation is cooperative
val job = launch {
while (isActive) { // check on each iteration
// do work
}
}
job.cancelAndJoin()
// Blocking work won't honour cancellation — wrap with withContext + check yield()
launch {
repeat(1_000_000) { i ->
if (i % 100 == 0) yield() // checkpoint
compute(i)
}
}
// 10) Timeouts
val result = withTimeoutOrNull(2_000) {
fetchSlow()
}
if (result == null) println("timed out")
// 11) Channels + Flow
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*
// Channels — for fan-out / fan-in producer/consumer
val ch = Channel<Int>()
launch { for (i in 1..3) ch.send(i); ch.close() }
for (v in ch) println(v)
// Flow — cold async stream; use for transformations
flow {
for (i in 1..3) {
delay(100)
emit(i)
}
}.collect { println(it) }
// 12) Coroutines in tests
import kotlinx.coroutines.test.*
@OptIn(ExperimentalCoroutinesApi::class)
@Test fun example() = runTest {
val result = async { doSomething() }
advanceTimeBy(1000) // skip delays
assertEquals(42, result.await())
}
// 13) Structured concurrency — golden rules
// • Every coroutine has a parent (Job) — defines lifetime
// • Cancelling a scope cancels all children
// • Awaiting children before scope completes
// • Don't 'leak' GlobalScope.launch — orphan; no cancellation hook
// 14) Common bugs
// • Forgot to launch — suspend function called from non-suspend → compile error
// • Suspending inside a Mutex without releasing — deadlock; use Mutex.withLock { }
// • Long-running coroutines without isActive/yield → can't cancel
// • GlobalScope.launch — bypasses structured concurrency; leak risk
// • Catch-all try/catch around async {} swallowing CancellationException — re-throw it
// • Misusing Dispatchers.Main on JVM — only available with Android or JavaFX integration
// • runBlocking inside a coroutine → blocks the dispatcher; refactor to nested suspend functions
// • async without await — value computed but discarded; use launch instead
// • Switching dispatcher inside a tight loop → overhead; switch ONCE around the loop
Why it matters
launch + async + withContext cover almost every coroutine flow: launch for fire-and-forget, async/await for parallel results, withContext to switch dispatchers. Use proper scopes (viewModelScope, lifecycleScope) so cancellation tracks UI lifetime, prefer supervisorScope when sibling failures shouldn’t cascade, and avoid GlobalScope.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
scope.launch { logEvent() } // fire-and-forget
val body = scope.async { fetch() }.await() // returns a result
Try it Yourself »
Discussion
Loading…