title: How to properly handle cancellation in coroutine's computation code?
body: <p>Here is my understanding of cancellation in coroutine:</p>
  
  <blockquote>
    <p>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.</p>
    
    <p>Except for SupervisorJob, it will continue active even though one of
    the child coroutines is stopped.</p>
  </blockquote>
  
  <p>So, I write a code snippet to practice my understanding.</p>
  
  <p><strong>Code Snippet 1</strong></p>
  
  <pre><code>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)
  }
  </code></pre>
  
  <p>Here are two of my expectations:</p>
  
  <p>Expectation 1:</p>
  
  <pre><code>#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.
  </code></pre>
  
  <p>Expectation 2:</p>
  
  <pre><code>#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.
  </code></pre>
  
  <p>However, the actual output of code snippet 1 is:</p>
  
  <pre><code>#4
  #1
  #2
  </code></pre>
  
  <p>I read <a href="https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html" rel="nofollow noreferrer">coroutine docs</a> again to find out that for computation code, we have to either use <code>yield</code> or check the coroutine state (<code>active</code>, <code>canceled</code>, <code>isCompleted</code>). Then I make the following adjustment:</p>
  
  <p><strong>Code Snippet 2</strong></p>
  
  <pre><code>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)
  }
  </code></pre>
  
  <p>This time the output is:</p>
  
  <pre><code>#4
  #1
  </code></pre>
  
  <p>Here are my questions:</p>
  
  <ol>
  <li><p>In code snippet 1, how is #2 still executed after <code>childJob</code> is canceled?</p></li>
  <li><p>In code snippet 1, why #3 is never executed even though <code>childJob</code> is called?</p></li>
  <li><p>In code snippet 2, do we really need to use <code>yield</code> or checking coroutine state every time we want to execute a coroutine code? Because in my opinion, the code will be harder to read.</p></li>
  <li><p>Is there something wrong my code snippet or my understanding of coroutine?</p></li>
  </ol>
  
  <p><strong>Note:</strong>
  I don't want to use <code>GlobalScope.runBlocking</code> for the code snippet, because in the real project, we don't use <code>GlobalScope</code> anyway. I want to create an example as close as what a real project should be, using parent-child scoping with some lifecycle.</p>