Aldo Wachyudi
08/03/2019, 2:49 AMIf 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
kotlin
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][1] 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
kotlin
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:
1. In code snippet 1, how is #2 still executed after childJob
is canceled?
2. In code snippet 1, why #3 is never executed even though childJob
is called?
3. 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.
4. 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.
[1]: https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.htmlKevin Gorham
08/03/2019, 3:11 AMdelay
instead of Thread.sleep
and you'll get the behavior you expect. If I get some time, I can explain why tomorrow (it's late in the night for me).Aldo Wachyudi
08/03/2019, 3:18 AMThread.sleep
to emulate blocking code in my project like fetching a JSON over network or compressing bitmap. For this, we should use delay()
instead of Thread.sleep
? Why the resulting behavior is different?Kevin Gorham
08/03/2019, 3:19 AMAldo Wachyudi
08/03/2019, 3:22 AMSo avoid thread.sleep and don’t catch the cancellation.Then, I don’t need to explicitly wrap my code with
try
and catch
for the sake of cancellation?Kevin Gorham
08/03/2019, 3:23 AMisActive
(meaning you have to participate in cooperative cancellation "manually") but yeah, I'm sure others will step in to help🤞. I have to hop off.
Here, this might help...Aldo Wachyudi
08/06/2019, 5:21 PMThread.sleep
with a suspending function that fetch a data over network, like this:
fun main() = runBlocking {
val parentScope = CoroutineScope(SupervisorJob())
parentScope.launch {
println("#1")
launch {
println("#3")
fetchDataOverNetwork()
println("#4")
}
println("#2")
}
delay(1_000)
parentScope.cancel()
delay(1_000)
println("Bye")
}
suspend fun fetchDataOverNetwork() {
val tenSecondInMillis = 10000
val result = URL("<https://httpstat.us/200?sleep=$tenSecondInMillis>").readText()
println("$result")
}
Now, the output is:
#1
#2
#3
Bye
#4 is never printed because the coroutine is already cancelled.cancellation exception
part. If I put try-catch on the code.
fun main() = runBlocking {
val parentScope = CoroutineScope(SupervisorJob())
parentScope.launch {
try {
println("#1")
launch {
try {
println("#3")
fetchDataOverNetwork()
println("#4")
} catch (e: Exception) {
println("Child's Exception not printed")
} finally {
println("Child's Finally not printed")
}
}
println("#2")
} catch (e: Exception) {
println("Parent's Exception not printed")
} finally {
println("Parent's Finally is printed")
}
}
delay(1_000)
parentScope.cancel()
delay(1_000)
println("Bye")
}
The output is:
#1
#2
Parent's Finally is printed
#3
Bye
Why is the exception and finally block is not executed? Even though in the documentation it says..
They check for cancellation of coroutine and throw CancellationException when cancelled.Under what condition the
CancellationException
is thrown?streetsofboston
08/06/2019, 6:02 PMAldo Wachyudi
08/07/2019, 2:59 AM