Demystify coroutineScope (not CoroutineScope)
Tags: kotlin, android, androiddev, coroutines
Published: 2024-04-09 16:37:59

In this article we will deep dive into coroutineScope (it’s small c)

- Difference between coroutineScope and CoroutineScope?
- Difference between coroutineScope and supervisorScope?
- Difference between coroutineScope and withContext?

### Terminologies

Before diving into the topic, we need to know some basic terminologies:

**Coroutine**: Framework to manage concurrency, written on top of the actual threading framework. In simplest term, piece of code that can be suspended and resumed without blocking the thread.

**Scope**: A scope in coroutines defines the lifetime and context of a coroutine. Each coroutine runs inside some scope.

**CoroutineBuilder**: Functions responsible for creating and starting a new coroutine. e.g., `launch{}`, `async{}`, `runBlocking{}`

**Suspend Function**: Function that can suspend and resume the code execution without blocking the thread. It can be call from suspend function or coroutine block only.

**CoroutineContext**: Provide contextual information(`CoroutineDispatcher`, `Job`, `CoroutineExceptionHandler`) for the coroutine. It also tells coroutine on which thread it need to run.

**CoroutineExceptionHandler**: Part of CoroutineContext to handle uncaught exceptions. Should be used in the root coroutine to handle all the child coroutine uncaught exceptions.

## coroutineScope vs CoroutineScope

### CoroutineScope

CoroutineScope is just an interface that have `coroutineContext` object. Each coroutine required some scope to run.

```Kotlin
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}
// Creates CoroutineScope and wraps the given context
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())
```

CoroutineScope constructor need CoroutineContext which then combines with the coroutine builder to start a new coroutine.

Say we want to perform some background task, we can simply do:

```Kotlin
CoroutineScope(Dispatchers.IO).launch {
  doSomeBackgroundTask()
}
```

It is straightforward, but what if we want to make few parallel api calls, we can do like this:

```Kotlin
CoroutineScope(Dispatchers.IO).launch {
    try {
        launch { postSchoolData() }
        launch { postTeacherData() }
        launch { postStudentData() }
    } catch (e: Exception) {
        log("Catch exception: " + e.message)
    }
}
```

Great, it looks good, but there is one catch, what if one api call failed (say `postTeacherData()`), what will happen?

App will crash, yes even writing inside the try-catch block, app will crash.

Reason behind this is how coroutines handles the error. When there is an exception in normal function it just re-throws the exception, but that is not the case with coroutines.

In coroutines if there is an exception occurs inside child coroutine, it propagates to the parent. In our example, if error occurs in inner launch, it will propagate to the outer launch block inspite of having try-catch block.

Since in our outer launch block, we are not passing any exception handler, our app is crashing. So one solution is using exception handler, but there is one more better way to do it — **coroutineScope**

### coroutineScope

```Kotlin
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = ScopeCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }
}
```

Looking at the source code, it can be seen coroutineScope is just a suspend function that creates a sub-scope in the coroutine hierarchy.

- It takes the caller’s CoroutineContext and unlike normal launch it does not propagate exceptions from its children but re-throws them instead.
- It cancels all other children if one of them fails
- It blocks the current coroutine until all of its child coroutines are completed.

> `coroutineScope{}` re-throws exceptions of its failing children instead of propagating them up the job hierarchy.

```Kotlin
CoroutineScope(Dispatchers.IO).launch {
    try {
        coroutineScope {
            launch { postSchoolData() }
            launch { postTeacherData(true) } //this throws exception
            launch { postStudentData() }
        }
    } catch (e: Exception) {
        log("Catch exception: " + e.message)
    }
}
```

So in the above example, if the second child coroutine (`postTeacherData()`) throws an exception, rather than propagating to the parent coroutine, coroutineScope will re-throw the exception and when exception is re-thrown, it will catch inside the try-catch block.

But there is one drawback, in case there are multiple child of coroutineScope, failure of one child leads to cancellation of all other children.

Solution? — **supervisorScope**

## coroutineScope vs supervisorScope

```Kotlin
public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = SupervisorCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }
}

private class SupervisorCoroutine<in T>(
    context: CoroutineContext,
    uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
    override fun childCancelled(cause: Throwable): Boolean = false
}
```

`supervisorScope` creates a new independent nested sub-scope. It doesn’t re-throws exceptions neither it propagate exceptions to the parent coroutine scope. It is similar to say using `CoroutineScope` with `SupervisorJob` (Job whose children can fail independently).

- If one of the coroutines inside this scope fails, the others are not cancelled
- Coroutines created inside supervisorScope become top-level Coroutines (we can add a `CoroutineExceptionHandler` in them)

In order for a `CoroutineExceptionHandler` to have an effect, it must be installed either in the CoroutineScope or in a top-level coroutine.

In case of `supervisorScope`, as parent is not cancelled, way of handling error is different than `coroutineScope`. `supervisorScope` checks for exception handlers and propagate it to the handler without cancelling, but if it does not have any exception handler, it will crash the app.

```Kotlin
CoroutineScope(Dispatchers.IO).launch {
    try {
        supervisorScope {
            launch { postSchoolData() }
            launch { postTeacherData(true) } //this throws exception
            launch { postStudentData() }
        }
    } catch (e: Exception) {
        log("Catch exception: " + e.message)
    }
}
```

The above example will crash the application as supervisorScope creates top-level coroutine and we are not passing exception handler. But if we use `async` instead, it will hold the error, and if we call await() without try-catch it will fail the supervisorScope and supervisorScope will re-throw the error.

```Kotlin
CoroutineScope(Dispatchers.IO).launch {
    try {
        supervisorScope {
            val totalStudent = async { getStudentCount(true) } //this throws exception
            val totalTeacher = async { getTeacherCount() }
            try {
                totalStudent.await()
            } catch (e: Exception) {
                log("Await Exception: " + e.message)
            }
            try {
                totalTeacher.await()
            } catch (e: Exception) {
                log("Await Exception: " + e.message)
            }
        }
    } catch (e: Exception) {
        log("Catch exception: " + e.message)
    }
}
```

The above example will work perfectly well without any child failure or exception but if in the above example we do not write `totalStudent.await()` inside try-catch block, it will fail the supervisorScope and error will be re-thrown by the supervisorScope which will get catch by outer try-catch block.

## coroutineScope vs withContext

There is also one more `suspend` function `withContext(context) {}` which is used generally to switch context between running coroutines and it will only return when all it’s children completes.

coroutineScope ≡ withContext(this.coroutineContext)

In simple terms `coroutineScope` is equivalent to passing same parent context in `withContext`.

Source Code: [Github](https://github.com/khushpanchal/HappyCoding/blob/master/app/src/main/java/com/khush/happycoding/kotlin/KotlincoroutineScope.kt)

### Contact Me:
[LinkedIn](https://www.linkedin.com/in/khush-panchal-241098170/), [Twitter](https://twitter.com/KhushPanchal15)

Happy Coding ✌️