Kotlin 1.3のCoroutineのキャンセル⑤（キャンセルさせないブロック）

# 検証環境
この記事の内容は、以下の環境で検証しました。

* Intellij IDEA ULTIMATE 2018.2
* Kotlin 1.3.0
* Gradle Projectで作成
* GradleファイルはKotlinで記述（KotlinでDSL）

# 準備
詳細は下記の準備を参照してください。
https://qiita.com/naoi/items/8abf2cddfc2cb3802daa

# Run non-cancellable block
[前回](https://qiita.com/naoi/items/d3355a7201d4819a14ae)に引き続き、[公式サイト](http://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#run-non-cancellable-block)を読み解いていきます。

今回のタイトルは「キャンセルさせない」です。これまではキャンセルさせることに注力していましたが、今回はキャンセルさせないってことでしょうか。

早速、読んでいきます。

> Any attempt to use a suspending function in the finally block of the previous example causes CancellationException, because the coroutine running this code is cancelled. Usually, this is not a problem, since all well-behaving closing operations (closing a file, cancelling a job, or closing any kind of a communication channel) are usually non-blocking and do not involve any suspending functions. However, in the rare case when you need to suspend in the cancelled coroutine you can wrap the corresponding code in withContext(NonCancellable) {...} using withContext function and NonCancellable context as the following example shows:

意訳込みですが、訳してみると以下のようになります。

> サスペンド関数実行中にキャンセルするとCancellationExceptionが発生します。これはfinally句でも同じです。finally句でよく記述するリソースの開放などは非ブロッキング関数なので、例外は発生しません。しかし、たまにですが、finally句でサスペンド関数を呼び出すことがあります。そのようなケースでは、例外が発生し、finally句の処理が途中で中断されてしまいます。回避する方法として、withContext関数とNonCancellableを使用することで一時的に非ブロッキングにできます。詳細は、サンプルコードをみてください。

確かに、finally句でサスペンド関数を呼ぶとキャンセルされそうですね。それも回避できるのは嬉しいです。早速、サンプルコードを確認してみます。

```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            withContext(NonCancellable) {
                println("I'm running finally")
                delay(1000L)
                println("And I've just delayed for 1 sec because I'm non-cancellable")
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
}
```

finally句にwithContext関数が呼び出されていますね。
あと、注目する点としては、withContext(NonCancellable) 関数内でdelay関数が呼び出されています。
delay関数はサスペンド関数なので、通常だったらキャンセルされて、「"And I've just delayed for 1 sec because I'm non-cancellable"」は表示されない。しかし、今回は表示されるはず！
実行してみました。

```text
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
I'm running finally
And I've just delayed for 1 sec because I'm non-cancellable
main: Now I can quit.
```

逆に、withContext(NonCancellable) 関数で囲まないとどうなるのか気になりますね。
サンプルコードを以下のように修正してみました。

```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            println("I'm running finally")
            delay(1000L)
            println("And I've just delayed for 1 sec because I'm non-cancellable")

        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
}
```

実行結果を確認してみると、以下のようになりました。

```text
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
I'm running finally
main: Now I can quit.
```

確かに、「And I've just delayed for 1 sec because I'm non-cancellable」が表示されていない。
delay関数がキャンセルされたと考えられます。

# まとめ
このブロックで理解できたことは以下のことだと思います。

* サスペンド関数で キャンセルを一時的に無効にすることができる
* withContext(NonCancellable) 内は一時的にキャンセルを無効にできる