Home > front end >  Doc: Is it correct that the thread running runBlocking blocks?
Doc: Is it correct that the thread running runBlocking blocks?

Time:09-18

The docs on Your first coroutine say -

The name of runBlocking means that the thread that runs it (in this case — the main thread) gets blocked for the duration of the call, until all the coroutines inside runBlocking { ... } complete their execution

Is it correct that the thread running runBlocking blocks?

If the main thread is blocked, then how the coroutine launched by launch { } in the below code (from the same doc) runs on the main thread??

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch { // launch a new coroutine and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println(Thread.currentThread().name   " World!") // print after delay
    }
    println(Thread.currentThread().name   " Hello") // main coroutine continues while a previous one is delayed
}

My understanding is that

  • launch{ } created a child coroutine in runBlocking scope
  • and runBlocking waits for the completion of this child coroutine, unlike builders like launch{ } or async { } that immediately return after creating the new coroutine
  • but runBlocking frees the main thread
  • since main thread is free, the new coroutine created by runBlocking also runs on main thread

Here is an output from the above code -

main @coroutine#1 Hello
main @coroutine#2 World!

Same issue in the documentation of runBlocking

Runs a new coroutine and blocks the current thread until its completion. This function should not be used from a coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.

CodePudding user response:

Compare the documentation you posted for runBlocking

Runs a new coroutine and blocks the current thread until its completion.

and launch

Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a Job.

They both create a new coroutine, it's just that launch runs its coroutine asynchronously, and allows the caller to continue running code. runBlocking doesn't allow the caller to continue until everything running in that coroutine has finished. So runBlocking doesn't control the behaviour of the stuff inside its coroutine, just the execution of the code that's calling runBlocking.

This tweak to your code might make things clearer:

fun main() {
    runBlocking {
        launch {
            delay(1000L)
            println(Thread.currentThread().name   " World!")
        }
        println(Thread.currentThread().name   " Hello")
    }
    // this -follows- the runBlocking statement
    println("Hey I'm runnin' here!")
}
main @coroutine#1 Hello
main @coroutine#2 World!
Hey I'm runnin' here!

So what's happening is:

  • runBlocking runs, blocking the main thread. The next line won't run until everything in this coroutine has finished!
  • launch runs, creating a new coroutine on the same thread, but without blocking it. The code on the next line will be able to run while this is running, because they're in separate coroutines, but taking turns in little time slices (since they're on the same thread). This coroutine calls delay immediately, so even if it runs before the line in the original coroutine (the println after launch) it'll definitely hit its own println much later
  • the println following launch runs, since this coroutine is independent of the other one. This coroutine is finished (but the other one is still running)
  • the println inside launch runs after its delay. This coroutine has nothing else to do, so it's also finished
  • since all the Jobs in the coroutine launched by runBlocking have finished, that blocking coroutine ends, and execution can continue in the scope where it was launched
  • that last println gets to run, finally!

Also just to clear up a possible misunderstanding:

Is it correct that the thread running runBlocking blocks?

If the main thread is blocked, then how the coroutine launched by launch { } in the >below code (from the same doc) runs on the main thread??

All coroutines run on the same thread as the caller, unless you explicitly tell them not to, or use a dispatcher that can move them to a different thread. It's just that the coroutines "take turns" and get a bit of time to execute some code on a thread. So it looks like they're running "at the same time" even when they're not.

So because launch doesn't "block", what that means is the coroutine that calls it is allowed to continue, getting a bit of time to execute the rest of the code, while the launched coroutine also gets some time slots. They can run independently.

But runBlocking does block, which means it doesn't get any execution time until the coroutine it started finishes. It takes a break until the coroutine is done. So it's not actually blocking the main thread in the normal sense, otherwise none of the coroutines on that thread could run! It's just blocking execution in its own scope - it's a way to say "nothing else happens here until this job has finished". That's why it's a common main function wrapper, to kind of create a world of coroutines that everything runs in. But it does block anything outside that from running, which is why it's a bad idea in something like Android, where there's other stuff going on with the main thread besides any coroutines you might want to run.

Also, launch actually runs on a CoroutineScope (i.e. CoroutineScope.launch) , which it has access to inside a coroutine - that's why you have to use it inside one! runBlocking is a standalone builder function, but the function block you pass in is a CoroutineScope.() -> T - i.e. it's going to be *passed to a CoroutineScope, like a default one provided by the framework. That CoroutineScope is what works out when all its child coroutines have finished, and informs the caller that it's done. runBlocking just waits around for that to happen, instead of allowing execution to continue like the coroutineScope builder.

CodePudding user response:

After searching for an answer since I posted this question, and reading through several articles and @cactustictacs answer, I think the words the current thread gets blocked puts readers in a mental block forcing them to think that the thread is doing nothing.

While coroutineScope just suspends, releasing the underlying thread for other usages, runBlocking {...} doesn't takes itself off the thread while waiting for its body and all the child coroutines to finish; that's why it is a normal function and not a suspending one. Since all child coroutines inherit the same context (& dispatcher, unless another one is passed) from runBlocking, they will continue on the same thread. So yes, the thread is blocked waiting for runBlocking to finish, but indeed it is busy executing runBlocking body and all its children coroutines.

Also, from documentation of runBlocking

The default [CoroutineDispatcher] for this builder is an internal implementation of event loop that processes continuations in this blocked thread until the completion of this coroutine.

The thread underlying runBlocking is busy executing an event loop that processes runBlocking body and all its child coroutines (those that inherit the context from runBlocking). Here is a beautiful Medium post explaining this phantom eventloop Learning Kotlin Coroutines as a Java Dev (Part II)

So, the blocked thread just means that the thread is busy executing runBlocking body, and won't come out unless everything runBlocking does and starts finishes.

It is same as saying in plain English - I am blocked researching this topic and won't get up to do anything else unless I solve this mystery and post an answer about it

  • Related