runBlocking Coroutine doesn't block GlobalScope.launch (?) - kotlin

Kotlin's runBlocking Coroutine is supposed to block the current thread until the Coroutine inside the block completes execution but it doesn't seem to be doing so when the Coroutine inside the block is GlobalScope.launch
I am trying to understand how Kotlin's Coroutines work and reading the documentation here - https://kotlinlang.org/docs/reference/coroutines/basics.html
In the example -
fun main() = runBlocking<Unit> { // start main coroutine
GlobalScope.launch { // launch new coroutine in background and continue
delay(1000L)
println("World!")
}
println("Hello,") // main coroutine continues here immediately
delay(2000L) // delaying for 2 seconds to keep JVM alive
}
It is mentioned that "The main thread, that invokes runBlocking, blocks until the coroutine inside runBlocking completes". If this is so then why do we need the two second delay to block the main thread at the end of the runBlocking? Why doesn't runBlocking doesn't block the main thread until the GlobalScope.launch completes?
However the following inner runBlocking blocks main thread until the delay function completes. What's the difference here? Why doesn't runBlocking in the above block main thread until GlobalScope.launch completes in a similar way-
fun main(){ // start main coroutine
GlobalScope.launch { // launch new coroutine in background and continue
delay(1000L)
println("World!")
}
println("Hello,") // main coroutine continues here immediately
runBlocking{
delay(2000L) // delaying for 2 seconds to keep JVM alive
}
}
I expect that when the main function is wrapped in a runBlocking co-routine, the main thread should be blocked until GlobalScope.launch completes its execution.

A coroutine in a scope will block until all it's children (jobs) in the same scope will have finished. However explicitely launching coroutines in another scope will not make them real children, and therefore they are not awaited.
This article also provides a bit of information around this particular case: https://medium.com/#elizarov/the-reason-to-avoid-globalscope-835337445abc

Related

Difference between starting coroutine with launch or with coroutineScope

It seems I can start a coroutine using a number of mechanisms. Two of them are coroutineScope and launch but reading the Kotlin coroutine docs I am unclear what the difference is and when I would use one over the other
fun main() {
println("Main block ${Thread.currentThread().name}")
runBlocking {
coroutineScope {
println("Coroutine scope ${Thread.currentThread().name}")
}
launch {
println("Launch ${Thread.currentThread().name}")
}
}
}
I ran the above and can see that both start a new coroutine. What is the difference?
A CoroutineScope is an organisational thing - it lets you group coroutines together to enforce structured concurrency. Basically, it allows you to control the lifetime of everything within the scope (including coroutines launched within other coroutines) and handles things like propagating results and errors, and cancelling everything within a scope. More info here
All the coroutine builder functions (including launch) run inside a CoroutineScope (or on it as a receiver, if you want to look at it that way):
fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
So the launch call in your example is running inside the current CoroutineScope, which has been provided by the runBlocking builder:
expect fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
See how the lambda you're passing is actually a CoroutineScope.() -> T? It runs with a CoroutineScope as the receiver - that's what you're calling launch on. You can't call it from just anywhere!
So when you create a CoroutineScope in your example, you're nesting one inside this CoroutineScope that runBlocking is providing. runBlocking won't allow the rest of the code that follows it to continue (i.e. it blocks execution) until everything in its CoroutineScope has completed. Creating your own CoroutineScope inside there lets you define a subset of coroutines that you can control - you can cancel them, but if there are still other coroutines running in the runBlocking scope, it will keep blocking until they're done as well.
Like Louis says in the other answer, you're not actually creating any coroutines inside the scope you've created. The code is just running in the coroutine that runBlocking started to run the block of code you passed. Check this out:
fun main() {
println("Main")
runBlocking {
coroutineScope {
delay(1000)
println("I'm in a coroutine scope")
}
launch {
delay(500)
println("First new coroutine")
}
launch {
delay(50)
println("Second new coroutine")
}
}
}
Main
I'm in a coroutine scope
Second new coroutine
First new coroutine
Code within a single coroutine executes sequentially - so what happens is
You start in main
You enter the runBlocking block's coroutine
You create a CoroutineScope which only affects stuff you launch (etc) on it
You delay, then print a line
You hit the first launch and create a new coroutine. This starts off by delaying 500 ms, but it's running on its own now - different coroutines run concurrently
You move onto the next line, and launch the second coroutine. That runs on its own too, and immediately delays 50ms. There are 3 coroutines running now
You've finished executing the code in the block - this coroutine is done. However, there are two running coroutines that were also started on this CoroutineScope, so runBlocking waits for those to finish.
The second coroutine with the shortest delay prints first, then finishes
The first coroutine finishes last, because even though it was started earlier, they all run independently
Now everything inside the runBlocking CoroutineScope is done, it can finish and allow execution to contine in main()
If you launched a coroutine inside that scope you created, it would all work the same way - it's just you'd have fine-grained control over the stuff in there in particular, and you could specifically work with the coroutines inside that nested scope without affecting things in the outer scope. All that matters is once that inner scope has completed, it can inform the outer scope that it's all done, that kind of thing
coroutineScope doesn't start a new coroutine.
launch starts a new coroutine that can run concurrently with other coroutines. coroutineScope does not.

Kotlin unhandled coroutine exception does not crash

I expected the following program to crash but it doesn't. It just prints that an exception occurred in a thread and exits normally. Why?
import kotlinx.coroutines.*
fun main() {
val scope = CoroutineScope(Dispatchers.Default)
scope.launch {
delay(100)
throw RuntimeException("1")
}
println("sleep")
Thread.sleep(1000)
println("exit without crash")
}
playground
Since you're creating your own CoroutineScope, you have your own top-level coroutine which is not bound to the main thread. Creating a CoroutineScope manually also implies manual cleanup (otherwise you have the same pitfalls as GlobalScope and may leak coroutines or lose errors).
The main program only crashes if the main thread crashes, and the main thread doesn't crash here. If you want to have a relationship between this main thread and the launched coroutines (and propagate exceptions), you should use structured concurrency from the beginning.
For instance, using runBlocking at the top of your main() function creates a CoroutineScope (available as this) which serves as parent for all launched coroutines. It also blocks the main thread while waiting for all the child coroutines, and propagates exceptions. You don't need your own scope then:
fun main() {
runBlocking(Dispatchers.Default) { // using Dispatchers.Default to be equivalent with your code
launch { // using "this" CoroutineScope from runBlocking
delay(100)
throw RuntimeException("1")
}
println("sleep")
delay(1000) // using delay instead of Thread.sleep because we're in a coroutine now
println("exit without crash")
}
}
This way you get:
sleep
Exception in thread "main" java.lang.RuntimeException: 1
at FileKt$main$1$1.invokeSuspend (File.kt:16)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106)

Difference between withContext and suspendCancellableCoroutine

I'm new to coroutines
This is a popular example:
suspend fun findBigPrime(): BigInteger =
withContext(Dispatchers.IO) {
BigInteger.probablePrime(4096, Random()))
}
However, it could be written as well as:
suspend fun findBigPrime(): BigInteger =
suspendCancellableCoroutine {
it.resume(BigInteger.probablePrime(4096, Random()))
}
What's the real difference?
What's the real difference?
There's hardly any relationship, in fact.
suspendCancellableCoroutine {
it.resume(BigInteger.probablePrime(4096, Random()))
}
This does nothing but add useless overhead above the simple direct call
BigInteger.probablePrime(4096, Random())
If you resume the continuation while still inside the suspendCancellableCoroutine block, the coroutine doesn't suspend at all.
withContext(Dispatchers.IO) {
BigInteger.probablePrime(4096, Random()))
}
This suspends the coroutine and launches an internal coroutine on another thread. When the internal coroutine completes, it resumes the current one with the result.
Use of suspendCancellableCoroutine here is a "BIG NO".
withContext changes the context on which the block (coroutine) will run, here the Dispatcher which dispatches the coroutine to a specified thread is overridden. While the suspendCoroutine/suspendCancellableCoroutine are used for wrapping asynchronous callbacks which does not block the thread instead they run on thread of their own.
Usually work on the suspendCoroutine/suspendCancellableCoroutine is non-blocking and gets completed quite quickly and the continuation is resumed after the work has completed in a non-blocking way maybe in other thread or so.
If you put blocking code in there the concept of coroutine is lost, it will just going to block the thread it is running on.
Use of suspendCoroutine/suspendCancellableCoroutine:
// This function immediately creates and starts thread and returns
fun findBigPrimeInNewThread(after: (BigInteger) -> Unit) {
Thread {
BigInteger.probablePrime(4096, Random()).also(after)
}
}
// This just wraps the async function so that when the result is ready resume the coroutine
suspend fun findBigPrime(): BigInteger =
suspendCancellableCoroutine { cont ->
findBigPrimeInNewThread {
cont.resume(it)
}
}

Why do I still need to add job.join() after I have used runBlocking in Kotlin?

I'm learning the Coroutines of kotlin.
The Image A can get the correct result.
I think I have used the code runBlocking, and main function will keep runing until it get final result, but Image B failed, why?
Image A
Image B
When you use runBlocking, your code between the { } will run inside a CoroutineScope. If you run a child coroutine inside with launch, it will behave as you expected, because the parent coroutine has to wait for all its children before completing:
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello, ")
}
However, when you use GlobalScope.launch to launch a new coroutine, it will not be tied to any parent, and therefore it won't be waited for, unless you add job.join().
See this:
Children of a coroutine
When a coroutine is launched in the CoroutineScope of another
coroutine, it inherits its context via CoroutineScope.coroutineContext
and the Job of the new coroutine becomes a child of the parent
coroutine's job. When the parent coroutine is cancelled, all its
children are recursively cancelled, too.
However, when GlobalScope is used to launch a coroutine, there is no
parent for the job of the new coroutine. It is therefore not tied to
the scope it was launched from and operates independently.
runBlocking will not return until all coroutines in its own scope have finished. Since your job is running on the GlobalScope instead it does not wait until it is finished. job.join() forces it to wait until the job (even from another scope) is done.
If you remove GlobalScope. it will launch on its internal scope and will work as you expect it should.
From the kotlin documentation about coroutines basics:
Structured concurrency
There is still something to be desired for practical usage of
coroutines. When we use GlobalScope.launch, we create a top-level
coroutine. Even though it is light-weight, it still consumes some
memory resources while it runs. If we forget to keep a reference to
the newly launched coroutine it still runs. What if the code in the
coroutine hangs (for example, we erroneously delay for too long), what
if we launched too many coroutines and ran out of memory? Having to
manually keep references to all the launched coroutines and join them
is error-prone.
There is a better solution. We can use structured concurrency in our
code. Instead of launching coroutines in the GlobalScope, just like we
usually do with threads (threads are always global), we can launch
coroutines in the specific scope of the operation we are performing.
In our example, we have main function that is turned into a coroutine
using runBlocking coroutine builder. Every coroutine builder,
including runBlocking, adds an instance of CoroutineScope to the scope
of its code block. We can launch coroutines in this scope without
having to join them explicitly, because an outer coroutine
(runBlocking in our example) does not complete until all the
coroutines launched in its scope complete. Thus, we can make our
example simpler:
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch { // launch a new coroutine in the scope of runBlocking
delay(1000L)
println("World!")
}
println("Hello,")
}
Adding to the point made by #huytc
To better understand the parent-child hierarchy you can use the job.children.contains like below
val customScope = CoroutineScope(Dispatchers.Default)
fun main() = runBlocking {
val job = customScope.launch {
println("processing")
delay(1000)
println("done processing")
}
println("Is custom scope child of run-blocking: ${this.coroutineContext.job.children.contains(job)}")
Output:
processing
Is custom scope child of run-blocking: false

CoroutineScope blocks current thread

According to the Kotlin docs, a CoroutineScope does not block the current thread. They show this code example:
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope { // Creates a coroutine scope
launch {
delay(500L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope") // This line will be printed before the nested launch
}
println("Coroutine scope is over") // This line is not printed until the nested launch completes
}
The output is as follows:
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over
They even include the comment:
This line is not printed until the nested launch completes
And sure enough, that line doesn't print until the end. But this is what I don't understand. They say that the CoroutineScope does not block the current thread. So why doesn't this line print after the first launch is completed?:
println("Coroutine scope is over")
This line is not part of the coroutineScope. I would have expected the result to be:
Coroutine scope is over
Task from runBlocking
Task from coroutine scope
Task from nested launch