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 fun
s 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 asuspend fun
. The thread that called it remains inside it until the coroutine is complete. -
coroutineScope
is asuspend fun
. If your coroutine suspends, thecoroutineScope
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” thecoroutineScope
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 aDispatcher
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 theasync
work happening inside it, get the final result, and handle all failures at one central place.