
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”: launch
, async
, 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.