How do I run coroutines in parallel using async and await in Kotlin?

In Kotlin, use async to start concurrent work inside a coroutine scope, then call await() to get each result.

Basic example:

import kotlinx.coroutines.*

suspend fun fetchUser(): String {
    delay(1000)
    return "User"
}

suspend fun fetchPosts(): List<String> {
    delay(1000)
    return listOf("Post 1", "Post 2")
}

fun main() = runBlocking {
    val userDeferred = async {
        fetchUser()
    }

    val postsDeferred = async {
        fetchPosts()
    }

    val user = userDeferred.await()
    val posts = postsDeferred.await()

    println(user)
    println(posts)
}

Here, fetchUser() and fetchPosts() run in parallel. Since both delay for 1 second, the total time is about 1 second instead of 2.

You can also await multiple results with awaitAll:

import kotlinx.coroutines.*

fun main() = runBlocking {
    val deferredResults = listOf(
        async {
            delay(1000)
            "Result 1"
        },
        async {
            delay(1000)
            "Result 2"
        },
        async {
            delay(1000)
            "Result 3"
        }
    )

    val results = deferredResults.awaitAll()

    println(results)
}

Output:

[Result 1, Result 2, Result 3]

A common pattern is:

coroutineScope {
    val a = async { doWorkA() }
    val b = async { doWorkB() }

    val resultA = a.await()
    val resultB = b.await()

    combine(resultA, resultB)
}

For example:

import kotlinx.coroutines.*

suspend fun loadName(): String {
    delay(500)
    return "Alice"
}

suspend fun loadAge(): Int {
    delay(500)
    return 30
}

suspend fun loadProfile(): String = coroutineScope {
    val name = async { loadName() }
    val age = async { loadAge() }

    "${name.await()} is ${age.await()} years old"
}

fun main() = runBlocking {
    println(loadProfile())
}

Important notes:

  • async returns a Deferred<T>.
  • await() suspends until the result is ready.
  • async should usually be called inside coroutineScope, supervisorScope, runBlocking, or another coroutine.
  • If one async child fails inside coroutineScope, the whole scope is cancelled.
  • Use async for concurrent computations that return values.
  • Use launch for concurrent work that does not return a value.

If you are doing blocking I/O, use an appropriate dispatcher:

val data = async(Dispatchers.IO) {
    blockingNetworkCall()
}

For CPU-heavy work:

val result = async(Dispatchers.Default) {
    heavyCalculation()
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.