Home > front end >  Kotlin Coroutines: runBlocking vs. coroutineScope
Kotlin Coroutines: runBlocking vs. coroutineScope

Time:10-25

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?

CodePudding user response:

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 runBlocking doesn't. You're launching coroutines out of runBlocking and then immediately returning, so none of those coroutines have necessarily had a chance to even begin running by the time you return num from the function.

If you wanted to wait for all the coroutines launched inside a runBlocking lambda, 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.

CodePudding user response:

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.

  • Related