launch, async, runBlockingIn Kotlin, coroutine builders are functions that start new coroutines. The main builders are launch, async, and runBlocking. Each serves different purposes, return different types, and behave differently in terms of cancellation and threading.
launchWhat it does: Starts a coroutine for side-effects (fire-and-forget). Returns a Job to manage it.
val job = CoroutineScope(Dispatchers.Default).launch {
repeat(5) { i ->
if (!isActive) return@launch // cooperative cancellation
println("Working \$i on \${Thread.currentThread().name}")
delay(300)
}
}
// Cancel after 800ms
CoroutineScope(Dispatchers.Default).launch {
delay(800)
job.cancel()
println("Job cancelled")
}
Expected output:
Working 0 on DefaultDispatcher-worker-1
Working 1 on DefaultDispatcher-worker-1
Working 2 on DefaultDispatcher-worker-1
Job cancelled
asyncWhat it does: Starts a coroutine for computations and returns a Deferred<T> for the result. Use await() to get the value.
runBlocking {
val d1 = async(Dispatchers.Default) {
delay(1000)
"Result 1"
}
val d2 = async(Dispatchers.Default) {
delay(1200)
"Result 2"
}
println("Waiting for results...")
val r1 = d1.await()
val r2 = d2.await()
println("Results: \$r1, \$r2")
}
Expected output:
Waiting for results...
Results: Result 1, Result 2
runBlockingWhat it does: Blocks the current thread until the coroutine completes. Useful in main() or tests.
fun main() = runBlocking {
launch {
delay(500)
println("Inside runBlocking")
}
println("runBlocking will wait for children before returning")
}
Expected output:
runBlocking will wait for children before returning
Inside runBlocking
job.cancel() — Requests cancellation of a coroutine.job.join() — Suspends until the coroutine completes (either normally or with an exception).isActive — Use inside loops to check for cooperative cancellation.CoroutineExceptionHandler — Handles uncaught exceptions in launch coroutines.supervisorScope / SupervisorJob — Prevents one child failure from cancelling siblings.
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
repeat(5) { i ->
println("Working $i ...")
delay(300)
}
}
delay(700)
println("Cancelling job!")
job.cancel()
job.join() // waits for cancellation to complete
println("Job cancelled!")
}
Explanation: cancel() sends a cancellation signal, and join() suspends until the coroutine actually stops. Always use both for proper cleanup.
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
var i = 0
while (isActive) { // cooperative cancellation check
println("Processing item ${'$'}i")
i++
delay(200)
}
}
delay(600)
job.cancelAndJoin()
println("Gracefully stopped.")
}
Explanation: If you don’t use isActive, CPU-bound loops won’t respond to cancel() immediately.
import kotlinx.coroutines.*
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, ex ->
println("Caught exception: ${'$'}{ex.message}")
}
val scope = CoroutineScope(SupervisorJob() + handler)
with(scope) {
val job1 = launch {
println("Failing child")
throw RuntimeException("Something went wrong!")
}
val job2 = launch {
delay(500)
println("Sibling coroutine still running")
}
job1.join()
job2.join()
}
}
Explanation: Using SupervisorJob, even if one child fails, the other continues.
Without it, both coroutines would be cancelled.
DEFAULT — Starts immediately after creation.LAZY — Starts only when start(), join(), or await() is called.ATOMIC — Starts immediately and cannot be cancelled before first suspension.UNDISPATCHED — Executes immediately in the current thread until the first suspension point.
import kotlinx.coroutines.*
fun main() = runBlocking {
val lazyJob = launch(start = CoroutineStart.LAZY) {
println("Lazy job started in ${'$'}{Thread.currentThread().name}")
}
println("Job not started yet")
delay(300)
lazyJob.start() // or lazyJob.join()
println("Main finished")
}
Explanation: The coroutine with CoroutineStart.LAZY won't start until explicitly requested.
| Builder | Returns | Use-case | Cancellation / Exception Behavior |
|---|---|---|---|
launch |
Job |
Fire-and-forget tasks (e.g., network calls, saving data) | Exceptions cancel parent unless caught with CoroutineExceptionHandler |
async |
Deferred<T> |
Concurrent computations returning results | Exceptions rethrown on await(); cancels parent unless supervisor used |
runBlocking |
T |
Blocking bridge for main functions and tests | Blocks the current thread until all child coroutines complete |
import kotlinx.coroutines.*
fun main() = runBlocking {
// launch
val job = launch {
delay(500)
println("launch finished")
}
// async
val deferred = async {
delay(500)
"Result from async"
}
println("Async returned: ${'$'}{deferred.await()}")
job.join()
println("All done!")
}
Explanation: launch returns a Job (no result), while async returns a Deferred which can produce a result using await().