After reading the introduction and the javadoc of CoroutineScope I'm still a little confused what the idea behind a CoroutineScope
is.
The first sentence of the doc "Defines a scope for new coroutines." is not clear to me: Why do my coroutines need a scope?
Also, why are standalone coroutine builders deprecated? Why is it better to do this:
fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
for (x in 1..5) send(x * x)
}
instead of
fun produceSquares(): ReceiveChannel<Int> = produce { //no longer an extension function
for (x in 1..5) send(x * x)
}
A CoroutineScope keeps track of any coroutine it creates using launch or async . The ongoing work (i.e. the running coroutines) can be cancelled by calling scope. cancel() at any point in time. In Android, some KTX libraries provide their own CoroutineScope for certain lifecycle classes.
The easiest way to create a coroutine scope object is by using the CoroutineScope factory function 1. It creates a scope with provided context (and an additional Job for structured concurrency if no job is already part of the context).
CoroutineContext is an interface that represents an element or a collection of elements. It is conceptually similar to a map or a set collection: it is an indexed set of Element instances like Job , CoroutineName , CouroutineDispatcher , etc. The unusual thing is that each Element is also a CoroutineContext .
So when one wants to call any suspend functions such as delay() and do not care about the asynchronous nature, one can use the runBlocking function.
You can still use global "standalone" coroutines by spawning them in GlobalScope
:
GlobalScope.launch {
println("I'm running unstructured")
}
However, it's not recommended to do this since creating coroutines on a global scope is basically the same we did with good old threads. You create them but somehow need to keep track of a reference to later join/cancel them.
Using structured concurrency, that is nesting coroutines in their scopes, you will have a more maintainable system overall. For example, if you spawn a coroutine inside another one, you inherit the outer scope. This has multiple advantages. If you cancel the outer coroutine, the cancellation will be delegated to its inner coroutines. Also, you can be sure that the outer coroutine will not complete before all its children coroutines have done their work.
There's also a very good example shown in the documentation for CoroutineScope
.
CoroutineScope should be implemented on entities with well-defined lifecycle that are responsible for launching children coroutines. Example of such entity on Android is Activity.
After all, the first version of your shown produceSquares
methods is better as it is only executable if invoked in a CoroutineScope
. That means you can run it inside any other coroutine:
launch {
produceSquares()
}
The coroutine created inside produceSquares
inherits the scope of launch
. You can be sure that launch
does not complete before produceSquares
. Also, if you cancelled launch
, this would also effect produceSquares
.
Furthermore, you can still create a globally running coroutine like this:
GlobalScope.produceSquares()
But, as mentioned, that's not the best option in most cases.
I'd also like to promote an article I wrote. There are some examples demonstrating what scopes mean: https://kotlinexpertise.com/kotlin-coroutines-concurrency/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With