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?
CodePudding user response:
coroutineScope
doesn't start a new coroutine.
launch
starts a new coroutine that can run concurrently with other coroutines. coroutineScope
does not.
CodePudding user response:
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 youlaunch
(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
, sorunBlocking
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 inmain()
If you launch
ed 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