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?
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.
Related
When using withContext in Kotlin, how to keep the new context after calling another suspend function?
For example:
https://pl.kotl.in/fkrnEkYZP
fun main() = runBlocking {
val anotherBlock = suspend {
assertSame(Dispatchers.IO, coroutineContext[CoroutineDispatcher], "current CoroutineDispatcher should be still Dispatchers.IO")
// Exception in thread "main" java.lang.AssertionError: current CoroutineDispatcher should be still Dispatchers.IO. Expected <Dispatchers.IO>, actual <BlockingEventLoop#6863cde3> is not same.
}
withContext(Dispatchers.IO) {
assertSame(Dispatchers.IO, coroutineContext[CoroutineDispatcher], "current CoroutineDispatcher should be Dispatchers.IO")
anotherBlock()
}
}
Why is the coroutineContext in anotherBlock different with the parent one?
The problem is that you're not looking at the actual current coroutine context when doing the assert in the suspend lambda, you're in fact capturing the coroutineContext from runBlocking's CoroutineScope.
The reason why it works in withContext directly is because withContext provides a CoroutineScope as this - which shadows the one given by runBlocking, so luckily you get the correct one.
To avoid this pitfall (which occurs regularly when using both suspend functions and CoroutineScope), you should use currentCoroutineContext() when you want to know the coroutine context in which the current code is executing, which is your case here.
If you do this, you'll see the dispatcher is properly inherited:
fun main() = runBlocking {
val anotherBlock = suspend {
assertSame(Dispatchers.IO, currentCoroutineContext()[CoroutineDispatcher], "current CoroutineDispatcher should be still Dispatchers.IO in suspend lambda")
}
withContext(Dispatchers.IO) {
assertSame(Dispatchers.IO, coroutineContext[CoroutineDispatcher], "current CoroutineDispatcher should be Dispatchers.IO")
anotherBlock()
}
}
coroutineContext is a property of CoroutineScope. Since your anotherBlock suspend function does not have a CoroutineScope receiver, you are calling coroutineContext on the scope of the runBlocking lambda, so it's getting the runBlocking's dispatcher.
Change your declaration of the function to use extension function syntax instead of a lambda so it can have its own CoroutineScope.
suspend fun CoroutineScope.anotherBlock() {
assertSame(Dispatchers.IO, coroutineContext[CoroutineDispatcher], "current CoroutineDispatcher should be still Dispatchers.IO")
// Exception in thread "main" java.lang.AssertionError: current CoroutineDispatcher should be still Dispatchers.IO. Expected <Dispatchers.IO>, actual <BlockingEventLoop#6863cde3> is not same.
}
fun test(coroutineScope: CoroutineScope){
coroutineScope.launch {
//FUNC 1
}
CoroutineScope(Dispatchers.XXX).launch {
//FUNC 2
}
}
FUNC 1 Not working but FUNC 2 is work well!
I don't know the difference between the two.
This is because coroutineScope is a function which returns whatever the lambda block returns. And since it is a function, you need to invoke it like coroutineScope(block = { ... }) or coroutineScope { ... }. While in your 2nd case, CoroutineScope(Dispatchers.XXX) returns a CoroutineScope and since launch is an extension function on CoroutineScope, it is valid.
The names are actually a bit confusing. coroutineScope is a function which takes a suspending lambda with CoroutineScope as the receiver while CoroutineScope(context) is a function which creates a CoroutineScope.
coroutineScope is generally used to capture the current coroutineContext inside a suspend function. For example,
suspend fun uploadTwoFiles() {
// Here we need to launch two coroutines in parallel,
// but since "launch" is an extension function on CoroutineScope,
// we need to get that CoroutineScope from somewhere.
coroutineScope {
// Here we have a CoroutineScope so we can call "launch"
launch { uploadFile1() }
launch { uploadFile2() }
}
// Also, note that coroutineScope finishes only when all the children coroutines have finished.
}
Using coroutineScope has this advantage that the coroutines launched inside will get cancelled when the parent coroutine cancels.
I'm learning concurrency in Kotlin, coming from C#/JavaScript background, and I can't help comparing some concepts.
In C# and JavaScript, technically we can rewrite an async function as a regular non-async version doing the same thing, using Task.ContinueWith or Promise.then etc.
The caller of the function wouldn't even notice the difference (I ranted about it in a blog post).
Is something like that possible for a suspend function in Kotlin (i.e., without changing the calling code)? I don't think it is, but I thought I'd still ask.
The closest thing I could come up with is below (Kotlin playground link), I still have to call .await():
import kotlinx.coroutines.*
suspend fun suspendableDelay(ms: Long): Long {
delay(ms);
return ms;
}
fun regularDelay(ms: Long): Deferred<Long> {
val d = CompletableDeferred<Long>()
GlobalScope.async { delay(ms); d.complete(ms) }
return d;
}
suspend fun test(ms: Long): Long {
delay(ms);
return ms;
}
fun main() {
val r1 = runBlocking { suspendableDelay(250) }
println("suspendableDelay ended: $r1");
val r2 = runBlocking { regularDelay(500).await() }
println("regularDelay ended: $r2");
}
https://pl.kotl.in/_AmzanwcB
If you're on JVM 8 or higher, you can make a function that calls the suspend function in an async job and returns a CompletableFuture, which can be used to get your result with a callback (thenApplyAsync()) or synchronously (get()).
val scope = CoroutineScope(SupervisorJob())
suspend fun foo(): Int {
delay(500)
return Random.nextInt(10)
}
fun fooAsync(): CompletableFuture<Int> = scope.async { foo() }.asCompletableFuture()
fun main() {
fooAsync()
.thenApplyAsync { println(it) }
Thread.sleep(1000)
}
The above requires the kotlinx-coroutines-jdk8 library.
I don't know of a solution that works across multiple platforms.
This can only work if you change your suspending function to a non-suspending blocking function, for example
private fun method(){
GlobalScope.launch {
val value = getInt()
}
}
// Calling coroutine can be suspended and resumed when result is ready
private suspend fun getInt(): Int{
delay(2000) // or some suspending IO call
return 5;
}
// Calling coroutine can't be suspended, it will have to wait (block)
private fun getInt(): Int{
Thread.sleep(2000) // some blocking IO
return 5;
}
Here you can simply use the non-suspending version, without any change on the caller.
But the issue here is that without suspend modifier the function becomes blocking and as such it can not cause the coroutine to suspend, basically throwing away the advantage of using coroutiens.
What's the difference between CoroutineScope(dispatchers).launch{} and coroutineScope{ launch{}}?
Say I have the code below:
(you can go to Kotlin playground to run this snippet https://pl.kotl.in/U4eDY4uJt)
suspend fun perform(invokeAfterDelay: suspend () -> Unit) {
// not printing
CoroutineScope(Dispatchers.Default).launch {
delay(1000)
invokeAfterDelay()
}
// will print
coroutineScope {
launch {
delay(1000)
invokeAfterDelay()
}
}
}
fun printSomething() {
println("Counter")
}
fun main() {
runBlocking {
perform {
printSomething()
}
}
}
And as the comment stated, when using CoroutineScope().launch it won't invoke the print, however when using the other way, the code behaves as intended.
What's the difference?
Thanks.
Further question
New findings.
if I leave the perform function like this (without commenting out one of the coroutines)
suspend fun perform(invokeAfterDelay: suspend () -> Unit) {
CoroutineScope(Dispatchers.Default).launch {
delay(1000)
invokeAfterDelay()
}
coroutineScope {
launch {
delay(1000)
invokeAfterDelay()
}
}
}
then both of these coroutines will be executed 🤔Why?
CoroutineScope().launch {} and coroutineScope { launch{} } have almost nothing in common. The former just sets up an ad-hoc scope for launch to run against, completing immediately, and the latter is a suspendable function that ensures that all coroutines launched within it complete before it returns.
The snippet under your "Further question" is identical to the original one except for the deleted comments.
Whether or not the first coroutine prints anything is up to non-deterministic behavior: while perform is spending time within coroutineScope, awaiting for the completion of the inner launched coroutine, the first one may or may not complete itself. They have the same delay.
Given we have a suspending function but this is not a CoroutineScope, how can we launch other coroutines such that those are associated with the current scope of whatever runs this suspending function?
Every suspendable function has access to the global variable coroutineContext which you can trivially wrap in CoroutineScope, but that is not its intended purpose. It's there so you can check at any point whether your coroutine was cancelled, reach the debug info like the job name, etc.
In the words of Roman Elizarov in his recent Medium post:
suspend fun doNotDoThis() {
CoroutineScope(coroutineContext).launch {
println("I'm confused")
}
}
Do not do this!
A suspendable function should not fire off concurrent work that may go on after it returns. It should only use concurrency to achieve parallel decomposition of a task, and that means it will wait for all the child coroutines to complete.
You should decide to either use a plain function that is the receiver of CoroutineScope (signaling the intention to launch concurrent work) or use a suspendable function that awaits on the completion of all the work it initiated.
So, if you want parallel decomposition, then use a coroutineScope or, possibly a supervisorScope block:
coroutineScope {
launch {
// ... task to run in the background
}
// ... more work while the launched task runs in parallel
}
// All work done by the time we reach this line
coroutineScope is a suspendable function and it won't complete until all the coroutines it launched complete.
You can create an extension function on CoroutineScope or function with CoroutineScope as a parameter:
fun CoroutineScope.doThis() {
launch { ... }
}
fun doThatIn(scope: CoroutineScope) {
scope.launch { ... }
}
Also you can use coroutineScope or supervisorScope, depending on your needs:
suspend fun someFun() = coroutineScope {
launch { ... }
}
suspend fun someFun() = supervisorScope {
launch { ... }
}
You could just use withContext() or coroutineScope() for launching another coroutine:
withContext(coroutineContext) {
launch { ... }
}
While the second would override the Job of the context, but reuse the context:
coroutineScope {
launch { ... }
}