A suspend fun can pause at a suspension point, a call to another suspending function, and resume later, possibly on another thread; only coroutines and other suspend functions can call it. Builders start coroutines: launch returns a Job to join() or cancel(); async returns a Deferred<T>, whose await() suspends for the result; runBlocking blocks its thread, so it belongs in main, never on Android's main thread.
import kotlinx.coroutines.*
suspend fun fetchPrice(i: Int): Double { delay(300); return catalog[i].price } // "network"
fun main() = runBlocking {
log("start")
val job = launch { log("launch got ${fetchPrice(5)}") }
val a = async { fetchPrice(0) }; val b = async { fetchPrice(2) }
log("async total ${a.await() + b.await()}; job completed: ${job.isCompleted}")
}Output
0 ms [main @coroutine#1] start 431 ms [main @coroutine#2] launch got 21.3 449 ms [main @coroutine#1] async total 38.99; job completed: true
The three 300 ms fetches overlapped on one thread, with no await keyword on fetchPrice. The compiler turns each suspend fun into a state machine resumed through a hidden Continuation parameter.