Difference between CoroutineScope and coroutineScope in Kotlin

Kushal picture Kushal · Dec 17, 2019 · Viewed 7.5k times · Source

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.

Answer

Kushal picture Kushal · Dec 17, 2019

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