jhamm
jhamm

Reputation: 25062

Kotlin coroutines not running in parallel?

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?

Upvotes: 1

Views: 2217

Answers (1)

Tenfour04
Tenfour04

Reputation: 93872

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.

Upvotes: 3

Related Questions