Reputation: 1986
I am playing around with Kotlin Coroutines, and I ended up in a situation I do not understand. Let's say that I have two suspend
functions:
suspend fun coroutine() {
var num = 0
coroutineScope {
for (i in 1..1000) {
launch {
delay(10)
num += 1
}
}
}
println("coroutine: $num")
}
and:
suspend fun runBlocked() = runBlocking {
var num = 0
for (i in 1..1000) {
launch {
delay(10)
num += 1
}
}
println("Run blocking: $num")
}
Then I call them from main()
method:
suspend fun main() {
coroutine()
runBlocked()
}
The coroutine()
method prints (as expected) a number that is almost never 1000 (usually between 970 and 999). And I understand why.
What I do not understand is why the runBlocked()
function allways prints 0.
coroutine: 998
runBlocked: 0
I tried one more time, this time making a similar function to runBlocked()
, with the difference that this time the method is returning a value instead of printing:
suspend fun runBlockedWithReturn(): Int = runBlocking {
var num = 0
for (i in 1..1000) {
launch {
delay(10)
num += 1
}
}
return@runBlocking num
}
And then I called it from the main()
method:
suspend fun main() {
val result = runBlockedWithReturn()
println("Run blocking with return: $result")
}
...but the method returned 0.
Why is that? And how do I fix the runBlocked()
method to print a number that is close to 1000 instead of 0? What am I missing?
Upvotes: 1
Views: 1662
Reputation: 93591
runBlocking
must never be called from a coroutine in the first place. Since we are violating this contract by putting it in a suspend function, any explanation we have for why it's behaving the way it is might be different on different platforms or in the future.
Aside from that, blocking code should never be called in a coroutine unless you are in a CoroutineContext that uses a dispatcher that can handle it, like Dispatchers.IO
.
That said, the reason this is happening is that coroutineScope
suspends until all of its children coroutines finish, and then you are logging after it returns. runBlocking
behaves similarly, but you are logging from inside the block without waiting.
If you wanted to wait for all the coroutines launched before logging, you need to join()
each of them. You can put them in a list and call joinAll()
on it. For example:
(1..1000).map {
launch {
delay(10)
num += 1
}
}.joinAll()
But again, runBlocking
should never be called in a coroutine. I'm only describing how you would do it if you were using runBlocking
from outside a coroutine for its intended purpose, to bridge between non-coroutine and coroutine code.
Upvotes: 2
Reputation: 28332
Existing answers focus on what we should not do, but I think they miss the point, so the main reason why we see the difference.
Both coroutineScope()
and runBlocking()
guarantee that after exiting the code block all coroutines inside already finished running. But for some reason, I don't know if intentional or not, you wrote both cases differently. In coroutine()
example you put println()
below coroutineScope()
block, so it is guaranteed to run after all children. On the other hand, in runBlocked()
you put println()
inside runBlocking()
, so it runs concurrently to children. Just rewrite your runBlocked()
in a similar way to coroutine()
, so put println()
below runBlocking()
and you will see 1000
, as you expected.
Another difference between both examples is that by default runBlocking()
runs using a single thread while coroutineScope()
could use many of them. For this reason coroutineScope()
produces a random value which is a result of unsafe sharing of a mutable state. runBlocking()
is more predictable. It always produces 0
if println()
is inside it, because println()
runs before any children. Or it always produces 1000
if println()
is below runBlocking()
, because children are in fact running one at a time, they don't modify the value in parallel.
Upvotes: 3
Reputation: 198033
You should never call runBlocking
from inside a suspend fun
.
In any event, you don't wait for the launched coroutines to finish before you return a value from runBlocking
, but your use of coroutineScope
forces the launched coroutines in that function to complete before you get to the return.
Upvotes: 2