Difference between CoroutineScope and coroutineScope in Kotlin
Asked Answered
V

6

32

Can anyone give clarity between functions CoroutineScope() and coroutineScope()?

When I tried to check in source, I found that both of them are functions of CoroutineScope.kt. Additionally, coroutineScope() is suspend function while other one is normal function

Below is documentation I could find :

/**
 * Creates a [CoroutineScope] that wraps the given coroutine [context].
 *
 * If the given [context] does not contain a [Job] element, then a default `Job()` is created.
 * This way, cancellation or failure or any child coroutine in this scope cancels all the other children,
 * just like inside [coroutineScope] block.
 */
@Suppress("FunctionName")
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

And

/**
 * Creates a [CoroutineScope] and calls the specified suspend block with this scope.
 * The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides
 * the context's [Job].
 *
 * This function is designed for _parallel decomposition_ of work. When any child coroutine in this scope fails,
 * this scope fails and all the rest of the children are cancelled (for a different behavior see [supervisorScope]).
 * This function returns as soon as the given block and all its children coroutines are completed.
 * A usage example of a scope looks like this:
 *
 * ```
 * suspend fun showSomeData() = coroutineScope {
 *
 *   val data = async(Dispatchers.IO) { // <- extension on current scope
 *      ... load some UI data for the Main thread ...
 *   }
 *
 *   withContext(Dispatchers.Main) {
 *     doSomeWork()
 *     val result = data.await()
 *     display(result)
 *   }
 * }
 * ```
 *
 * The scope in this example has the following semantics:
 * 1) `showSomeData` returns as soon as the data is loaded and displayed in the UI.
 * 2) If `doSomeWork` throws an exception, then the `async` task is cancelled and `showSomeData` rethrows that exception.
 * 3) If the outer scope of `showSomeData` is cancelled, both started `async` and `withContext` blocks are cancelled.
 * 4) If the `async` block fails, `withContext` will be cancelled.
 *
 * The method may throw a [CancellationException] if the current job was cancelled externally
 * or may throw a corresponding unhandled [Throwable] if there is any unhandled exception in this scope
 * (for example, from a crashed coroutine that was started with [launch][CoroutineScope.launch] in this scope).
 */
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = ScopeCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }

I want to get clear difference between them. If anyone can answer when to use which one, it would be helpful.

Vernation answered 17/12, 2019 at 6:42 Comment(0)
V
47

Best difference between CoroutineScope (Capital C version) vs coroutineScope (Smaller c version), I could figure out and which was easily understandable was correlating them with Unstructured vs Structured concurrency

Let me share an example :

class MainActivity : AppCompatActivity() {
    private lateinit var btn: Button
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn = findViewById(R.id.start_btn)
        btn.setOnClickListener {
            CoroutineScope(Dispatchers.Main).launch {
                val result = downloadUserData()
                Toast.makeText(applicationContext, "Result : $result", Toast.LENGTH_LONG).show()
            }
        }
    }

    private suspend fun downloadUserData(): Int {
        var result = 0
        // Here, we use CoroutineScope (Capital C version) which will start a new scope and
        // launch coroutine in new scope Dispatchers.IO, Not In Parent Scope which is Dispatchers.Main
        // Thus, this function would directly return without waiting for loop completion and will return 0
        CoroutineScope(Dispatchers.IO).launch {
            for (i in 0 until 100) {
                kotlinx.coroutines.delay(10)
                result++
            }
        }
        return result
    }
}

Output : Result : 0

This is an example of Unstructured Concurrency where it is not guaranteed that child coroutine would complete before returning. Thus, caller/parent coroutine would get wrong value returned by child coroutine. Even, when child coroutine has returned already, child coroutine may be running (in Active state) in the background which may lead to Memory Leaks in certain cases.

Solution :

When we need to communicate between multiple coroutines, we need to make sure Structured Concurrency (Recommended)

This can be done by re-using parent/caller coroutine scope inside child/callee coroutine. This can be achieved by coroutineScope {} (Smaller c) version inside child/callee coroutine.

private suspend fun downloadUserData(): Int {
    var result = 0
    // By using coroutineScope (Smaller c version) below, we ensure that this coroutine would execute in the
    // parent/caller coroutine's scope, so it would make sure that the for loop would complete
    // before returning from this suspended function. This will return 20000 properly
    coroutineScope {
        for (i in 0 until 100) {
            kotlinx.coroutines.delay(10)
            result++
        }
    }
    return result
}

Output : Result : 100

Vernation answered 17/12, 2019 at 14:46 Comment(5)
Why it looks partly like java code? E.g. int result = 0 instead of var result: Int = 0 or extends keyword instead of : On the other hand you can see suspend keyword which is from Kotlin language. What's going on here?Procarp
@Kushal, I modified this half-koitlin code to kotlin code. If you keep the original post, please revert it. I tested this code and I think it seems to suit to your purpose.Leupold
Won't be the same if you just remove completely coroutineScope?Irrefragable
Best explanation !! Upvoting.Joist
@Irrefragable yeah, i wonder in the first place why we need to create child coroutine with coroutineScope global function like this then, if we will get the same result without it..Verst
L
24

CoroutineScope() is nothing but a factory of CoroutineScope objects, and a CoroutineScope object is nothing but a holder of a CoroutineContext. It has no active role in coroutines, but it's an important part of the infrastructure that makes it easy to do structured concurrency properly. This comes from the fact that all coroutine builders like launch or async are extension functions on CoroutineScope and inherit its context.

You will rarely, if ever, have the need to call CoroutineScope() because usually you either pick up an existing coroutine scope or have one created for you by other convenience functions (like MainScope on Android) or Kotlin internals.

coroutineScope(), on the other hand, is a function that executes the block you pass it inside a sub-coroutine. It is basically an alias for withContext(this.coroutineContext) and you should primarily use it when you want to launch one or more background coroutines while you continue some work in the foreground, and then join on the background coroutines when completing the block.

Lophobranch answered 17/12, 2019 at 9:23 Comment(12)
So, coroutineScope() and withContext() are considered coroutine builders?Florettaflorette
Nice answer, but it is not true that coroutineScope is basically an alias for withContext(this.coroutineContext): they are not the same thing. withContext will NOT wait for sub-coroutines you lunch inside it to complete, coroutineScope willNeighbor
@DanieleSegato Have you tried it, or do you say it based on the docs only? Because the docs are outdated, withContext now does wait for the completion of child coroutines.Lophobranch
Oh, you are right! No i did not try it, I just looked at the code and it looked like they do different things (I answer you in more details here)Neighbor
You will rarely, if ever, have the need to call CoroutineScope()” - this is not true, at least not on the JVM. If you want to launch a coroutine from a regular function that you don’t want to wait for, you’ll need to use CoroutineScope.Append
@AbhijitSarkar In that case you would actually use the GlobalScope singleton.Lophobranch
@MarkoTopolnik "you would actually use the GlobalScope" - I wouldn't. The reason to avoid GlobalScope.Append
@AbhijitSarkar You would be doing exactly the same mistakes covered in that blog if you used CoroutineScope().launch. They are almost synonymous.Lophobranch
@MarkoTopolnik What is the difference between suspend fun getData() { } and suspend fun getData() = coroutineScope { } ?Rightly
If you try writing launch { println("hello") } inside each of these, you'll notice one of the differences. In the former it's a compile error, in the latter one it works.Lophobranch
@MarkoTopolnik and would it be right to say that some functions, like delay() do not need the explicit outer coroutineContext {}, because they implicitly take the coroutineContext from continuation? Like inside delay() function: cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)Oriental
@southerton Yes, there is always a coroutine context available whenever you are within a suspendable function.Lophobranch
N
9

They are two completely different things.

CoroutineScope is the interface that define the concept of Coroutine Scope: to launch and create coroutines you need a one.

GlobalScope is a instance of scope that is global for example.

CoroutineScope() is a global function that creates a CoroutineScope

When you have a scope you can do launch() or async() or any other method related to executing coroutines.

// create a context
val myContext = Dispacher.IO
// you can combine dispachers, parent jobs etc.
// create the new scope
val myScope: CoroutineScope = CoroutineScope(myContext)
// returns immediately (unless you specify a start mode that run immediately)
val job = myScope.launch {
  // suspend calls are allowed here cause this is a coroutine
}
// this code is executed right away

you can do this from outside a coroutine (plain code).

coroutineScope() on the other hand is an global suspend function that creates a new CoroutineScope under the hood and then execute the suspend function you pass with it in the body, and wait for it (and all its children) to complete before returning. It is a suspend function so you cannot call it outside of a coroutine.

// must be inside a coroutine here!

// this create a new CoroutineScope,
// then launch the given coroutine,
// then wait for it to complete
val result = coroutineScope {
   // your coroutine here, which run immediately
   return@coroutineScope "my result"
}
// this code is executed after the coroutine above is completed
// I can use "result" here

similar to coroutineScope there's supervisedScope which has only 1 difference: multiple children coroutines (launch / async / ...) executed inside it will not cancel other children if one fails cause it use a SupervisorJob

Neighbor answered 2/9, 2020 at 9:8 Comment(0)
A
3

coroutineScope is used for Structured concurrency. Structured concurrency guarantees:

  • When a scope cancels, all of its coroutines cancel.
  • When a suspend function returns, all of its work is done.
  • When a coroutine errors, its caller or scope is notified.

The coroutineScope unlike the CoroutineScope, will suspend itself until all coroutines started inside of it are complete. Indeed it creates a local scope and guarantees that all child coroutines will be completed before the return instruction.

Authorize answered 25/6, 2022 at 6:29 Comment(0)
A
2

CoroutineScope() is the method which takes a Context as input and gives you Context with a Job as an object of CoroutineScope interface.

You can use this object to launch a coroutine job as following:

suspend fun doNotDoThis() {
  CoroutineScope(coroutineContext).launch {
      println("I'm confused")
  }
}

While, coroutineScope() takes a block/labmda to execute as a coroutine job:

   fun main() = runBlocking { // this: CoroutineScope
    launch { 
        delay(200L)
        println("Task from runBlocking")
    }

    coroutineScope { // Creates a new coroutine scope
        launch {
            delay(500L) 
            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
}

References:

Article-1

Article-2

Kotlin-Docs

I hope this answers your questions.

Adolescent answered 17/12, 2019 at 7:2 Comment(0)
M
1

In the Unstructured concurrency example if you replace Launch builder with Async and await on the deferred, it will work same as the example you have used in Structured Concurrency. Your answer is still unclear. Explain the actual use of structured concurrency (Which is useful in exception and error handling, when one of the child Job throws an exception, which should not effect other childrens(Jobs))

Missioner answered 28/5, 2020 at 16:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.