Is there any difference between this two approaches?
runBlocking {
launch(coroutineDispatcher) {
// job
}
}
GlobalScope.launch(coroutineDispatcher) {
// job
}
runBlocking runs new coroutine and blocks current thread interruptibly until its completion. This function should not be used from 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.
// line 1
runBlocking {
// line 2
launch(coroutineDispatcher) {
// line 3
}
// line 4
}
// line 5
someFunction()
In case of using runBlocking lines of code will be executed in the next order:
line 1
line 2
line 4
line 3
line 5 // this line will be executed after coroutine is finished
Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely. Another use of the global scope is operators running in Dispatchers.Unconfined, which don't have any job associated with them.
Application code usually should use application-defined CoroutineScope, using async or launch on the instance of GlobalScope is highly discouraged.
// line 1
GlobalScope.launch(coroutineDispatcher) {
// line 2
}
// line 3
someFunction()
In case of using GlobalScope.launch lines of code will be executed in the next order:
line 1
line 3
line 2
Thus runBlocking blocks current thread until its completion, GlobalScope.launch doesn't.
I will try a different explanation without repeating the existing standard answers.
The "coroutine scope" is the boundary where the coroutine exists. The "global scope" exists as long as the application process is running. If you use "GlobalScope.launch()" you create a global coroutine that lives in the application scope.
If you use "runBlocking { launch() }" you create a coroutine that lives in the local block just after runBlocking. That local block will not exit as long as the coroutines in it are alive.
Related
Using ktor HTTP server, I would like to launch a long-running task and immediately return a message to the calling client. The task is self-sufficient, it's capable of updating its status in a db, and a separate HTTP call returns its status (i.e. for a progress bar).
What I cannot seem to do is just launch the task in the background and respond. All my attempts at responding wait for the long-running task to complete. I have experimented with many configurations of runBlocking and coroutineScope but none are working for me.
// ktor route
get("/launchlongtask") {
val text: String = (myFunction(call.request.queryParameters["loops"]!!.toInt()))
println("myFunction returned")
call.respondText(text)
}
// in reality, this function is complex... the caller (route) is not able to
// determine the response string, it must be done here
suspend fun myFunction(loops : Int) : String {
runBlocking {
launch {
// long-running task, I want to launch it and move on
(1..loops).forEach {
println("this is loop $it")
delay(2000L)
// updates status in db here
}
}
println("returning")
// this string must be calculated in this function (or a sub-function)
return#runBlocking "we just launched $loops loops"
}
return "never get here" // actually we do get here in a coroutineScope
}
output:
returning
this is loop 1
this is loop 2
this is loop 3
this is loop 4
myFunction returned
expected:
returning
myFunction returned
(response sent)
this is loop 1
this is loop 2
this is loop 3
this is loop 4
Just to explain the issue with the code in your question, the problem is using runBlocking. This is meant as the bridge between the synchronous world and the async world of coroutines and
"the name of runBlocking means that the thread that runs it ... gets blocked for the duration of the call, until all the coroutines inside runBlocking { ... } complete their execution."
(from the Coroutine docs).
So in your first example, myFunction won't complete until your coroutine containing loop completes.
The correct approach is what you do in your answer, using CoroutineScope to launch your long-running task. One thing to point out is that you are just passing in a Job() as the CoroutineContext parameter to the CoroutineScope constructor. The CoroutineContext contains multiple things; Job, CoroutineDispatcher, CoroutineExceptionHandler... In this case, because you don't specifiy a CoroutineDispatcher it will use CoroutineDispatcher.Default. This is intended for CPU-intensive tasks and will be limited to "the number of CPU cores (with a minimum of 2)". This may or may not be want you want. An alternative is CoroutineDispatcher.IO - which has a default of 64 threads.
inspired by this answer by Lucas Milotich, I utilized CoroutineScope(Job()) and it seems to work:
suspend fun myFunction(loops : Int) : String {
CoroutineScope(Job()).launch {
// long-running task, I want to launch it and move on
(1..loops).forEach {
println("this is loop $it")
delay(2000L)
// updates status in db here
}
}
println("returning")
return "we just launched $loops loops"
}
not sure if this is resource-efficient, or the preferred way to go, but I don't see a whole lot of other documentation on the topic.
I was reading Coroutine Basics trying to understand and learn it.
There is a part there with this code:
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope { // Creates a new coroutine scope
launch {
delay(900L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope") // This line will be printed before nested launch
}
println("Coroutine scope is over") // This line is not printed until nested launch completes
}
The output goes like so:
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over
My question is why this line:
println("Coroutine scope is over") // This line is not printed until nested launch completes
is called always last?
Shouldn't it be called since the:
coroutineScope { // Creates a new coroutine scope
....
}
is suspended?
There is also a note there:
The main difference between runBlocking and coroutineScope is that the latter does not block the current thread while waiting for all children to complete.
I dont understand how coroutineScope and runBlocking are different here? coroutineScope looks like its blocking since it only gets to the last line when it is done.
Can anyone enlighten me here?
I don't understand how coroutineScope and runBlocking are different here? coroutineScope looks like its blocking since it only gets to the last line when it is done.
There are two separate worlds: the suspendable world (within a coroutine) and the non-suspendable one. As soon as you enter the body of runBlocking, you are in the suspendable world, where suspend funs behave like blocking code and you can't get to the next line until the suspend fun returns. coroutineScope is a suspend fun that returns only when all the coroutines inside it are done. Therefore the last line must print at the end.
I copied the above explanation from a comment which seems to have clicked with readers. Here is the original answer:
From the perspective of the code in the block, your understanding is correct. The difference between runBlocking and coroutineScope happens at a lower level: what's happening to the thread while the coroutine is blocked?
runBlocking is not a suspend fun. The thread that called it remains inside it until the coroutine is complete.
coroutineScope is a suspend fun. If your coroutine suspends, the coroutineScope function gets suspended as well. This allows the top-level function, a non-suspending function that created the coroutine, to continue executing on the same thread. The thread has "escaped" the coroutineScope block and is ready to do some other work.
In your specific example: when your coroutineScope suspends, control returns to the implementation code inside runBlocking. This code is an event loop that drives all the coroutines you started within it. In your case, there will be some coroutines scheduled to run after a delay. When the time arrives, it will resume the appropriate coroutine, which will run for a short while, suspend, and then control will be again inside runBlocking.
While the above describes the conceptual similarities, it should also show you that runBlocking is a completely different tool from coroutineScope.
runBlocking is a low-level construct, to be used only in framework code or self-contained examples like yours. It turns an existing thread into an event loop and creates its coroutine with a Dispatcher that posts resuming coroutines to the event loop's queue.
coroutineScope is a user-facing construct, used to delineate the boundaries of a task that is being parallel-decomposed inside it. You use it to conveniently await on all the async work happening inside it, get the final result, and handle all failures at one central place.
The chosen answer is good but fails to address some other important aspects of the sample code that was provided. For instance, launch is non-blocking and is suppose to execute immediately. That is simply not true. The launch itself returns immediately BUT the code inside the launch does appear to be put into a queue and is only executed when any other launches that were previously put into the queue have completed.
Here's a similar piece of sample code with all the delays removed and an additional launch included. Without looking at the result below, see if you can predict the order in which the numbers are printed. Chances are that you will fail:
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("1")
}
coroutineScope {
launch {
println("2")
}
println("3")
}
coroutineScope {
launch {
println("4")
}
println("5")
}
launch {
println("6")
}
for (i in 7..100) {
println(i.toString())
}
println("101")
}
The result is:
3
1
2
5
4
7
8
9
10
...
99
100
101
6
The fact that number 6 is printed last, even after going through nearly 100 println have been executed, indicates that the code inside the last launch never gets executed until all non-blocking code after the launch has completed. But that is not really true either, because if that were the case, the first launch should not have executed until numbers 7 to 101 have completed. Bottom line? Mixing launch and coroutineScope is highly unpredictable and should be avoided if you expect a certain order in the way things should be executed.
To prove that code inside launches is placed into a queue and ONLY executed after ALL the non-blocking code has completed, run this (no coroutineScopes are used):
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("1")
}
launch {
println("2")
}
launch {
println("3")
}
for (i in 4..100) {
println(i.toString())
}
println("101")
}
This is the result you get:
4
5
6
...
101
1
2
3
Adding a CoroutineScope will break this behavior. It will cause all non-blocking code that follows the CoroutineScope to not be executed until ALL code prior to the CoroutineScope has completed.
It should also be noted that in this code sample, each of the launches in the queue are executed sequentially in the order that they are added to the queue and each launch will only execute AFTER the previous launch executes. This may make it appear that all launches share a common thread. This is not true. Each of them is given their own thread. However, if any code inside a launch calls a suspend function, the next launch in the queue is started immediately while the suspend function is being carried out. To be honest, this is very strange behavior. Why not just run all the launches in the queue asynchronously? While I don't know the internals of what goes on in this queue, my guess is that each launch in the queue does not get its own thread but all share a common thread. It is only when a suspend function is encountered does it appear that a new thread is created for the next launch in the queue. It may be done this way to save on resources.
To summarize, execution is done in this order:
Code inside a launch is placed inside a queue and are executed in the order that they are added.
Non-blocking code following a launch executes immediately before anything in the queue is executed.
A CoroutineScope blocks ALL code following it BUT will execute all the launch coroutines in the queue before resuming to the code following the CoroutineScope.
runBlocking is for you to block the main thread.
coroutineScope is for you to block the runBlocking.
Well, after having read all the answers here, I found none of them answered the question beyond repeating the wording of the fragments of the documentation.
So, I went on to search for an answer elsewhere and found it here. It practically shows the difference in behavior of coroutineScope and runBlocking (i.e. the difference between suspending and blocking)
runBlocking just blocks the current thread until inner coroutines will be completed. Here, thread that executes runBlocking will be blocked until the coroutine from coroutineScope will be finished.
First launch just won't allow the thread execute instructions that come after runBlocking, but will allow proceed to the instructions that come immediately after this launch block - that's why Task from coroutine scope is printed before than Task from runBlocking.
But nested coroutineScope in the context of runBlocking won't allow the thread to execute instructions that come after this coroutineScope block, because runBlocking will block the thread until the coroutine from coroutineScope will be finished completely. And that's why Coroutine scope is over will always come after Task from nested launch.
From this wonderful article https://jivimberg.io/blog/2018/05/04/parallel-map-in-kotlin/
suspend fun <A, B> Iterable<A>.pmap(f: suspend (A) -> B): List<B> = coroutineScope {
map { async { f(it) } }.awaitAll()
}
With runBlocking, we were not using Structured Concurrency, so an invocation of f could fail and all other executions would continue unfazed. And also we were not playing nice with the rest of the code. By using runBlocking we were forcefully blocking the thread until the whole execution of pmap finishes, instead of letting the caller decide how the execution should go.
Kotlin says
runBlocking method blocks the current thread for waiting
coroutineScope just suspends, releasing the underlying thread for other usages.
hence runBlocking is a regular function and coroutineScope is a suspending function
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
}
in above example what i expect is :-
runBlocking blocks main thread and launch will executed and it comes to delay(200L)
So, underlying coroutine is released and runs coroutineScope and comes to delay(500L) & delay(100L)
So, again underlying coroutine is released and it should print println("Coroutine scope is over").
This is what my understanding on runBlocking and coroutineScope. Which is not working as expected.
the Output is
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over
Can anyone kindly explain in a easy way to understand this.
launch causes the block to be executed asynchronously, so the call to launch returns immediately, and the coroutine continues its running and doesn't wait for the execution of the launched block.
Therefore, immediately after runBlocking is called, the first and the second launch are called one after another, and immediately after that the coroutine is suspended on delay(100L).
After 100ms the coroutine is resumed and prints "Task from coroutine scope", and then the execution of the nested coroutine-scope's block ends. A coroutine-scope always waits for the end of execution of all the jobs it has launched, so it waits here for 500ms.
Meanwhile, the two launched blocks are executed, so "Task from runBlocking" is printed first (after 200ms from the start), and then "Task from nested launch" is printed (after 500ms from the start).
Eventually, after the internal launched job has been completed, the internal coroutine-scope finishes waiting, and the external coroutine continues and prints "Coroutine scope is over".
This is the story. I hope it helps a little to understand how the code is executed and why the order of printing is like that.
I modified the code a little
fun main() = runBlocking(Dispatchers.Default) {
var i = 1
launch {
println("Task from runBlocking")
while (i < 10) {
delay(30L)
println(i++)
}
}
coroutineScope { // Creates a coroutine scope
launch {
delay(200L)
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")
}
Output
Task from runBlocking
1
2
3
Task from coroutine scope
4
5
6
Task from nested launch
Coroutine scope is over
7
8
9
the observation i made is,
delay(100L) is equal to approximate 3 times delay(30L)
delay(200L) is equal to approximate 6 times delay(30L)
So, after 3 Task from coroutine scope and after 6 Task from nested launch is printed.
then exactly after this Coroutine scope is over but you can still see the loop printed 7,8,9.
This is because like runBlocking coroutineScope waits for all its members to execute by suspending underlying threads. But understand, those threads first work on members of coroutineScope not on runBlocking.
Hence, it is printing Task from coroutine scope and Task from nested launch before Coroutine scope is over
I thought that calling a "suspend" function from coroutine context using launch makes the call asynchronous. But in the example below I see that 2 invocations of placeOrder method are not running in the same thread one after another.
What is my mistake?
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File
fun main() = runBlocking {
t("1")
launch {
t("2")
placeOrder("C:\\Users")
t("3")
}
launch {
t("12")
placeOrder("C:\\Program Files")
t("13")
}
t("4")
}
fun t(s: String) {
val currentThread = Thread.currentThread()
println(s + ": " + currentThread.name + " " + currentThread.id)
}
suspend fun placeOrder(d:String): String {
t("placeOrder $d")
val user = createUser(d) // asynchronous call to user service
val order = createOrder(user) // asynchronous call to order service
t("placeOrder $d finished")
return order
}
suspend fun createUser(d:String): String {
t("createUser $d")
val toString = File(d).walk().map {
it.length()
}.sum().toString()
t("createUser $d finished")
return toString
}
suspend fun createOrder(user: String): String {
t("createOrder $user")
val toString = File("C:\\User").walk().map {
it.length()
}.sum().toString()
t("createOrder $user finished")
return toString
}
Output:
1: main 1
4: main 1
2: main 1
placeOrder C:\Users: main 1
createUser C:\Users: main 1
createUser C:\Users finished: main 1
createOrder 1094020270277: main 1
createOrder 1094020270277 finished: main 1
placeOrder C:\Users finished: main 1
3: main 1
12: main 1
placeOrder C:\Program Files: main 1
createUser C:\Program Files: main 1
createUser C:\Program Files finished: main 1
createOrder 5651227104: main 1
createOrder 5651227104 finished: main 1
placeOrder C:\Program Files finished: main 1
13: main 1
Instead of writing suspendable IO, you wrote blocking IO:
File(d).walk().map {
it.length()
}
Your functions never actually suspend and instead they block the single thread associated with their runBlocking dispatcher.
You gave your coroutines no opportunity to execute concurrently.
If you applied withContext(IO) { ... } around the above code, you'd get concurrency, but of the plain-old Java type, several threads being blocked in IO operations together.
The reason for this behavior is twofold:
All your coroutines are executed in the runBlocking scope, which is a single-threaded event loop. So this means only a single thread is ever used unless a different context is specified. (launch(Dispatchers.IO) as an example)
Even then it would be possible for the coroutines to interleave, except your coroutines do call suspending functions which actually have to suspend. This means it is effectively a normal sequential function call. If your functions included a yield() or delay(..) call you would see the coroutines interleave in execution.
launch function signature:
fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job (source)
As per the official Kotlin documentation link:
When launch { ... } is used without parameters, it inherits the context (and
thus dispatcher) from the CoroutineScope it is being launched from.
In your case, it inherits the context of the main runBlocking coroutine which runs in the main thread.
As coroutine context includes a coroutine dispatcher that determines what thread or threads the corresponding coroutine uses for its execution, so you can provide a different CoroutineContext with your launch coroutine builder. For Example:
fun main() = runBlocking {
t("1")
launch(Dispatchers.Default) {
t("2")
placeOrder("C:\\Users")
t("3")
}
launch(Dispatchers.Default) {
t("12")
placeOrder("C:\\Program Files")
t("13")
}
t("4")
}
Regarding suspend functions, a suspending function is just a regular Kotlin function with an additional suspend modifier which indicates that the function can suspend the execution of a coroutine. It doesn't make the call asynchronous by default.
You can execute your function code with custom dispatcher(e.g. IO dispatcher) using Kotlin's withContext() function as below:
suspend fun get(url: String) = withContext(Dispatchers.IO){/* Code for N/W logic */}
This will execute the body of the function in separate Thread than the calling coroutine context.
Here is 3-part series blog explaining usage of coroutines in Android apps :
https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb
replace launch with async
read this
Essentially the code above is running synchronously even without runBlocking!
Since all the coroutines are launched into the main running on a single thread, ultimately you can use IO dispatcher which uses multiple threads.
Also, note that multiple coroutines can run on a single thread but they are never executed in parallel, they may appear as running in parallel because of thread switching from one coroutine to another when a new coroutine is launched or suspended.
I was reading Coroutine Basics trying to understand and learn it.
There is a part there with this code:
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope { // Creates a new coroutine scope
launch {
delay(900L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope") // This line will be printed before nested launch
}
println("Coroutine scope is over") // This line is not printed until nested launch completes
}
The output goes like so:
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over
My question is why this line:
println("Coroutine scope is over") // This line is not printed until nested launch completes
is called always last?
Shouldn't it be called since the:
coroutineScope { // Creates a new coroutine scope
....
}
is suspended?
There is also a note there:
The main difference between runBlocking and coroutineScope is that the latter does not block the current thread while waiting for all children to complete.
I dont understand how coroutineScope and runBlocking are different here? coroutineScope looks like its blocking since it only gets to the last line when it is done.
Can anyone enlighten me here?
I don't understand how coroutineScope and runBlocking are different here? coroutineScope looks like its blocking since it only gets to the last line when it is done.
There are two separate worlds: the suspendable world (within a coroutine) and the non-suspendable one. As soon as you enter the body of runBlocking, you are in the suspendable world, where suspend funs behave like blocking code and you can't get to the next line until the suspend fun returns. coroutineScope is a suspend fun that returns only when all the coroutines inside it are done. Therefore the last line must print at the end.
I copied the above explanation from a comment which seems to have clicked with readers. Here is the original answer:
From the perspective of the code in the block, your understanding is correct. The difference between runBlocking and coroutineScope happens at a lower level: what's happening to the thread while the coroutine is blocked?
runBlocking is not a suspend fun. The thread that called it remains inside it until the coroutine is complete.
coroutineScope is a suspend fun. If your coroutine suspends, the coroutineScope function gets suspended as well. This allows the top-level function, a non-suspending function that created the coroutine, to continue executing on the same thread. The thread has "escaped" the coroutineScope block and is ready to do some other work.
In your specific example: when your coroutineScope suspends, control returns to the implementation code inside runBlocking. This code is an event loop that drives all the coroutines you started within it. In your case, there will be some coroutines scheduled to run after a delay. When the time arrives, it will resume the appropriate coroutine, which will run for a short while, suspend, and then control will be again inside runBlocking.
While the above describes the conceptual similarities, it should also show you that runBlocking is a completely different tool from coroutineScope.
runBlocking is a low-level construct, to be used only in framework code or self-contained examples like yours. It turns an existing thread into an event loop and creates its coroutine with a Dispatcher that posts resuming coroutines to the event loop's queue.
coroutineScope is a user-facing construct, used to delineate the boundaries of a task that is being parallel-decomposed inside it. You use it to conveniently await on all the async work happening inside it, get the final result, and handle all failures at one central place.
The chosen answer is good but fails to address some other important aspects of the sample code that was provided. For instance, launch is non-blocking and is suppose to execute immediately. That is simply not true. The launch itself returns immediately BUT the code inside the launch does appear to be put into a queue and is only executed when any other launches that were previously put into the queue have completed.
Here's a similar piece of sample code with all the delays removed and an additional launch included. Without looking at the result below, see if you can predict the order in which the numbers are printed. Chances are that you will fail:
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("1")
}
coroutineScope {
launch {
println("2")
}
println("3")
}
coroutineScope {
launch {
println("4")
}
println("5")
}
launch {
println("6")
}
for (i in 7..100) {
println(i.toString())
}
println("101")
}
The result is:
3
1
2
5
4
7
8
9
10
...
99
100
101
6
The fact that number 6 is printed last, even after going through nearly 100 println have been executed, indicates that the code inside the last launch never gets executed until all non-blocking code after the launch has completed. But that is not really true either, because if that were the case, the first launch should not have executed until numbers 7 to 101 have completed. Bottom line? Mixing launch and coroutineScope is highly unpredictable and should be avoided if you expect a certain order in the way things should be executed.
To prove that code inside launches is placed into a queue and ONLY executed after ALL the non-blocking code has completed, run this (no coroutineScopes are used):
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("1")
}
launch {
println("2")
}
launch {
println("3")
}
for (i in 4..100) {
println(i.toString())
}
println("101")
}
This is the result you get:
4
5
6
...
101
1
2
3
Adding a CoroutineScope will break this behavior. It will cause all non-blocking code that follows the CoroutineScope to not be executed until ALL code prior to the CoroutineScope has completed.
It should also be noted that in this code sample, each of the launches in the queue are executed sequentially in the order that they are added to the queue and each launch will only execute AFTER the previous launch executes. This may make it appear that all launches share a common thread. This is not true. Each of them is given their own thread. However, if any code inside a launch calls a suspend function, the next launch in the queue is started immediately while the suspend function is being carried out. To be honest, this is very strange behavior. Why not just run all the launches in the queue asynchronously? While I don't know the internals of what goes on in this queue, my guess is that each launch in the queue does not get its own thread but all share a common thread. It is only when a suspend function is encountered does it appear that a new thread is created for the next launch in the queue. It may be done this way to save on resources.
To summarize, execution is done in this order:
Code inside a launch is placed inside a queue and are executed in the order that they are added.
Non-blocking code following a launch executes immediately before anything in the queue is executed.
A CoroutineScope blocks ALL code following it BUT will execute all the launch coroutines in the queue before resuming to the code following the CoroutineScope.
runBlocking is for you to block the main thread.
coroutineScope is for you to block the runBlocking.
Well, after having read all the answers here, I found none of them answered the question beyond repeating the wording of the fragments of the documentation.
So, I went on to search for an answer elsewhere and found it here. It practically shows the difference in behavior of coroutineScope and runBlocking (i.e. the difference between suspending and blocking)
runBlocking just blocks the current thread until inner coroutines will be completed. Here, thread that executes runBlocking will be blocked until the coroutine from coroutineScope will be finished.
First launch just won't allow the thread execute instructions that come after runBlocking, but will allow proceed to the instructions that come immediately after this launch block - that's why Task from coroutine scope is printed before than Task from runBlocking.
But nested coroutineScope in the context of runBlocking won't allow the thread to execute instructions that come after this coroutineScope block, because runBlocking will block the thread until the coroutine from coroutineScope will be finished completely. And that's why Coroutine scope is over will always come after Task from nested launch.
From this wonderful article https://jivimberg.io/blog/2018/05/04/parallel-map-in-kotlin/
suspend fun <A, B> Iterable<A>.pmap(f: suspend (A) -> B): List<B> = coroutineScope {
map { async { f(it) } }.awaitAll()
}
With runBlocking, we were not using Structured Concurrency, so an invocation of f could fail and all other executions would continue unfazed. And also we were not playing nice with the rest of the code. By using runBlocking we were forcefully blocking the thread until the whole execution of pmap finishes, instead of letting the caller decide how the execution should go.