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 insiderunBlocking { ... }
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 callsdelay
immediately, so even if it runs before the line in the original coroutine (theprintln
afterlaunch)
it'll definitely hit its ownprintln
much later- the
println
followinglaunch
runs, since this coroutine is independent of the other one. This coroutine is finished (but the other one is still running) - the
println
insidelaunch
runs after its delay. This coroutine has nothing else to do, so it's also finished - since all the
Job
s in the coroutine launched byrunBlocking
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