Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Whats the concept behind a CoroutineScope?

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)
}
like image 780
morpheus05 Avatar asked Nov 21 '18 13:11

morpheus05


People also ask

What is the use of CoroutineScope?

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.

How do I create a CoroutineScope?

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).

What is a CoroutineContext?

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 .

When should I use runBlocking?

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.


1 Answers

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/

like image 140
s1m0nw1 Avatar answered Oct 05 '22 03:10

s1m0nw1