async and await

Getting a Result

async starts a coroutine that returns a value. It gives back a Deferred<T> (a future). Call await() to suspend until the value is ready.

Sequential:   |--price 300ms--|--stock 300ms--|          ≈ 600 ms
async/await:  |--price 300ms--|
              |--stock 300ms--|                          ≈ 300 ms

Sequential vs Concurrent

Example

Two independent calls. With async they run at the same time.

import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis

suspend fun getPrice(): Int { delay(300); return 499 }
suspend fun getStock(): Int { delay(300); return 12 }

fun main() = runBlocking {
    val seq = measureTimeMillis {
        val price = getPrice()
        val stock = getStock()
        println("Sequential: price=$price stock=$stock")
    }
    val conc = measureTimeMillis {
        val price = async { getPrice() }
        val stock = async { getStock() }
        println("Concurrent: price=${price.await()} stock=${stock.await()}")
    }
    println("Sequential took >= 600 ms: ${seq >= 600}")
    println("Concurrent took < 550 ms: ${conc < 550}")
}

Output

Sequential: price=499 stock=12
Concurrent: price=499 stock=12
Sequential took >= 600 ms: true
Concurrent took < 550 ms: true

awaitAll and Lazy async

Example

awaitAll waits for a list of Deferred. CoroutineStart.LAZY starts only when you call start() or await().

import kotlinx.coroutines.*

suspend fun rating(shop: String): Double {
    delay(100)
    return shop.length / 2.0
}

fun main() = runBlocking {
    val shops = listOf("Dominos", "KFC", "Haldiram")
    val ratings = shops.map { shop -> async { shop to rating(shop) } }.awaitAll()
    ratings.forEach { (shop, r) -> println("$shop -> $r") }

    val lazy = async(start = CoroutineStart.LAZY) {
        println("Lazy job running")
        42
    }
    println("Lazy not started yet")
    println("Lazy result: ${lazy.await()}")
}

Output

Dominos -> 3.5
KFC -> 1.5
Haldiram -> 4.0
Lazy not started yet
Lazy job running
Lazy result: 42

Rules

  • Use async only when you need a result and want concurrency. For a single call, just call the suspend function.
  • Always await() a Deferred, otherwise its exception may be lost or crash the scope.
  • async { }.await() right away is pointless: it is the same as calling the function directly.

Exercise: Three suspend functions each take 200 ms: loadBanner(), loadOffers(), loadCart(). Load all three concurrently and print the results. The whole thing should take about 200 ms.

Show answer
import kotlinx.coroutines.*

suspend fun loadBanner() = run { delay(200); "Diwali Sale" }
suspend fun loadOffers() = run { delay(200); listOf("10% off", "Free delivery") }
suspend fun loadCart() = run { delay(200); 3 }

fun main() = runBlocking {
    val banner = async { loadBanner() }
    val offers = async { loadOffers() }
    val cart = async { loadCart() }
    println("${banner.await()} | ${offers.await()} | cart=${cart.await()}")
}