Reputation: 25062
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
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