Here is my understanding of cancellation in coroutine:
If a parent coroutine is canceled, the children will stop too. If a child coroutine throws Exception, the sibling and parent coroutine will notice it and stop.
Except for SupervisorJob, it will continue active even though one of the child coroutines is stopped.
So, I write a code snippet to practice my understanding.
Code Snippet 1
fun main() {
val parentScope = CoroutineScope(SupervisorJob())
parentScope.launch {
val childJob = launch {
try {
println("#1")
Thread.sleep(1_000)
println("#2")
} catch (e: Exception) {
println("#3")
}
}
println("#4")
childJob.cancel()
}
Thread.sleep(2_000)
}
Here are two of my expectations:
Expectation 1:
#1 is called first because there's no blocking code between child and parent job.
#4 is called because `Thread.sleep` is blocking.
#3 is called because the childJob is cancelled, even though the coroutine is not finished.
Expectation 2:
#4 is called first because the parent coroutine start first.
#1 is called because even though the childJob is cancelled, there's time for #1 to be executed.
However, the actual output of code snippet 1 is:
#4
#1
#2
I read coroutine docs again to find out that for computation code, we have to either use yield
or check the coroutine state (active
, canceled
, isCompleted
). Then I make the following adjustment:
Code Snippet 2
fun main() {
val parentScope = CoroutineScope(SupervisorJob())
parentScope.launch {
val childJob = launch {
try {
println("#1")
Thread.sleep(1_000)
if (isActive) {
println("#2")
}
} catch (e: Exception) {
println("#3")
}
}
println("#4")
childJob.cancel()
}
Thread.sleep(2_000)
}
This time the output is:
#4
#1
Here are my questions:
In code snippet 1, how is #2 still executed after
childJob
is canceled?In code snippet 1, why #3 is never executed even though
childJob
is called?In code snippet 2, do we really need to use
yield
or checking coroutine state every time we want to execute a coroutine code? Because in my opinion, the code will be harder to read.Is there something wrong my code snippet or my understanding of coroutine?
Note:
I don't want to use GlobalScope.runBlocking
for the code snippet, because in the real project, we don't use GlobalScope
anyway. I want to create an example as close as what a real project should be, using parent-child scoping with some lifecycle.