iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…