Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between CoroutineScope and coroutineScope in Kotlin

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.

like image 786
Kushal Avatar asked Dec 17 '19 06:12

Kushal


People also ask

What is CoroutineScope in Kotlin?

Kotlin coroutines provide an API that enables you to write asynchronous code. With Kotlin coroutines, you can define a CoroutineScope , which helps you to manage when your coroutines should run. Each asynchronous operation runs within a particular scope.

What is the difference between GlobalScope and CoroutineScope?

GlobalScope is a top level CoroutineScope that is operating as long as the application is alive. We usually use this when we want to launch a running task on application scope so that the task will remains alive until the app killed. Since it's alive along with application lifetime, GlobalScope is a singleton object.

What is the difference between suspending vs blocking?

Hunting to know BLOCKING vs SUSPENDING A process is blocked when there is some external reason that it can not be restarted, e.g., an I/O device is unavailable, or a semaphore file is locked. A process is suspended means that the OS has stopped executing it, but that could just be for time-slicing (multitasking).

How do you make 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).


5 Answers

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

like image 104
Kushal Avatar answered Oct 28 '22 16:10

Kushal


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.

like image 37
Marko Topolnik Avatar answered Oct 28 '22 15:10

Marko Topolnik


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

like image 7
Daniele Segato Avatar answered Oct 28 '22 15:10

Daniele Segato


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.

like image 2
Harshvardhan Joshi Avatar answered Oct 28 '22 16:10

Harshvardhan Joshi


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

like image 1
parvez rafi Avatar answered Oct 28 '22 17:10

parvez rafi