2

I am trying to use Kotlin coroutines to run 2 queries in parallel. I get the result as expected, but they are running sequentially.

fun getSearchResults(
    sql: SqlBuilder,
): Pair<Int?, List<T>> {
    return runBlocking {
        val one = async {
            count(sql)
        }
        val two = async {
            query(sql)
        }

        Pair(one.await(), two.await())
    }
}

suspend fun count(sql: SqlBuilder): Int? {
    return namedParameterJdbcTemplate.queryForObject(sql.countSql, sql.params) { rs: ResultSet, _: Int ->
        rs.retrieveInteger("count")!!
    }
}

suspend fun query(sql: SqlBuilder): List<T> {
    return namedParameterJdbcTemplate.query(sql.sql, sql.params) { rs: ResultSet, _: Int ->
        createEntity(rs)
    }
}

Why is this not running in parallel? Also, why is my IDE telling me the suspend keywords are "redundant"? How do I fix this?

1
  • You cant run in parallel because runBlocking is "block" a single thread (the current thread) and all coroutines inside this block are running in that thread Commented Apr 8, 2022 at 15:52

1 Answer 1

3

When you launch a coroutine in runBlocking, the default dispatcher is a single-threaded dispatcher running in the thread that called runBlocking. You should not be using runBlocking anyway, since it defeats the purpose of using coroutines. Your getSearchResults function is a blocking function.

The reason suspend is redundant on your other functions is that those functions are not calling any other suspend functions. Aside from being redundant, the suspend keyword implies that the function will cooperate with cancellation and by convention does not block. Your implementation is breaking both of those rules because they do not call other suspend functions and they are calling blocking functions without wrapping them in withContext with an appropriate dispatcher, so they are themselves blocking functions. Blocking suspend functions must never exist by convention.

So the solution is to change getSearchResults to be a suspend function, and use coroutineScope instead of runBlocking in it so it will run its children coroutines in parallel and it will not block. If it is a suspend function, it won't be redundant for your other functions that call it to be marked suspend.

suspend fun getSearchResults(
    sql: SqlBuilder,
): Pair<Int?, List<T>> = coroutineScope {
    val one = async {
        count(sql)
    }
    val two = async {
        query(sql)
    }
    Pair(one.await(), two.await())
}

If count and query are blocking IO functions, you should use async(Dispatchers.IO) to call them. But if they are suspend functions you don't need to specify a dispatcher.

Sign up to request clarification or add additional context in comments.

2 Comments

If a function that calls suspending functions should itself be marked as suspend, does that imply that the suspend marker would be propagated all the way up the call chain, right up to fun main, or should there be a runBlocking at some stage?
It’s propagated up the call chain to a launched coroutine somewhere. That can be a launch, async, or runBlocking, among others. Typically, runBlocking would only be seen at the top level main of a CLI app unless it’s a project that has a mixture of coroutines and some other thread management strategy.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.