Home » Kotlin supervisorScope vs coroutineScope

Kotlin supervisorScope vs coroutineScope

  • by
Kotlin supervisorScope vs coroutineScope

1. Overview

In this article, we will learn the differences between supervisorScope vs coroutineScope of Kotlin.

To learn more about Kotlin, refer to these articles.

You can check this article to know the differences between launch and async coroutine builders.

2. Kotlin Coroutine

Kotlin coroutines are extremely inexpensive when compared with threads. Each time when we want to start a new task asynchronously, we can create a new coroutine. To start a new coroutine, we use one of the main “coroutine builders”: launchasync, or runBlocking.

2.1. Kotlin Coroutine scope

You can start new coroutines only inside a coroutine scope. launch and async are declared as extensions to CoroutineScope.

A coroutine scope helps you to manage when your coroutine should run and parent-child relationships between different coroutines. Each asynchronous operation runs within a particular scope.

2.2. Convention of Structured concurrency

Structured concurrency is the main difference between coroutineScope and supervisorScope

Coroutines follow a principle of structured concurrency so you can launch the new coroutines only in a coroutine scope that delimits the lifetime of the coroutine. 

In a real-world scenario, you might use a lot of coroutines. Structured concurrency ensures that all those coroutines are not lost and do not leak. An outer coroutine scope cannot complete until all its children coroutines are complete.

Structured concurrency also ensures that any errors in the code are properly reported and are never lost.

3. Kotlin coroutineScope vs supervisorScope

Coroutines create a parent-child relationships between different coroutines:

  • Parent Coroutine
    • Child coroutine 1
    • Child coroutine 2
    • Child coroutine N

If you want to maintain the structured concurrency, in the sense you want to fail the parent scope when any of its child coroutines fails, use coroutineScope. It also fails all other coroutines executing inside the parent scope.

Unlike coroutineScope, a failure of a child does not cause the parent scope to fail and does not affect its other children. We can also implement a custom policy for handling failures of its children.

4. coroutineScope child failures

For example, the following coroutineScope contains two coroutines executed by using the launch coroutine builder. We have explicitly causing the first coroutine to fail.

fun main()  = runBlocking {
    coroutineScope {
        println("main() method: ${Thread.currentThread().name}")
        val job: Job = launch { 
                    println("launch corotine: ${Thread.currentThread().name}")
                    delay(100)
                    val i = 100/0
                    println("launch corotine:  ${Thread.currentThread().name}")
                    "Hello World"
        }
        val job2: Job = launch { 
                    println("launch 2 corotine: ${Thread.currentThread().name}")
                    delay(110)
                    println("launch 2 corotine:  ${Thread.currentThread().name}")
                    "Hello World 2"
        }
        job.join()
        job2.join()
        println("main() method on ${Thread.currentThread().name}")
   }
}

If you execute the above code, any uncaught exception in the child coroutine causes both the parent scope and sibling coroutines to exit.

As you can see, the first coroutine failed with an exception and thus sibling coroutine also interrupted and cancelled.

main() method: main @coroutine#1
launch corotine: main @coroutine#2
launch 2 corotine: main @coroutine#3
Exception in thread "main" java.lang.ArithmeticException: / by zero
 at FileKt$main$1$1$job$1.invokeSuspend (File.kt:12) 
 at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33) 
 at kotlinx.coroutines.DispatchedTaskKt.resume (DispatchedTask.kt:234) 

5. Kotlin supervisorScope child failure

If you change from coroutineScope to supervisorScope, then any uncaught exception in children coroutine does not impact the parent scope or sibling coroutines.

fun main()  = runBlocking {
    supervisorScope {
        println("main() method: ${Thread.currentThread().name}")
        val job: Job = launch { 
                    println("launch corotine: ${Thread.currentThread().name}")
                    delay(100)
                    val i = 100/0
                    println("launch corotine:  ${Thread.currentThread().name}")
                    "Hello World"
        }
        val job2: Job = launch { 
                    println("launch 2 corotine: ${Thread.currentThread().name}")
                    delay(110)
                    println("launch 2 corotine:  ${Thread.currentThread().name}")
                    "Hello World 2"
        }
        job.join()
        job2.join()
        println("main() method on ${Thread.currentThread().name}")
   }
}

As you can see in the below logs, the exception in children did not cause the parent or sibling coroutines to cancel.

main() method: main @coroutine#1
launch corotine: main @coroutine#2
launch 2 corotine: main @coroutine#3
Exception in thread "main @coroutine#2" java.lang.ArithmeticException: / by zero
	at FileKt$main$1$1$job$1.invokeSuspend(File.kt:12)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
	at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
	at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
	at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
	at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
	at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:518)
	at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.common.kt:494)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
	at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
	at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
	at FileKt.main(File.kt:4)
	at FileKt.main(File.kt)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.base/java.lang.reflect.Method.invoke(Unknown Source)
	at executors.JavaRunnerExecutor$Companion.main(JavaRunnerExecutor.kt:27)
	at executors.JavaRunnerExecutor.main(JavaRunnerExecutor.kt)
	Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [CoroutineId(2), "coroutine#2":StandaloneCoroutine{Cancelling}@69d9c55, BlockingEventLoop@13a57a3b]
launch 2 corotine:  main @coroutine#3
main() method on main @coroutine#1

6. Conclusion

To sum up, we have learned the differences between the Kotlin supervisorScope vs coroutineScope. You can find code samples in our GitHub repository.

Leave a Reply

Your email address will not be published.