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

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

今回のタイトルはわかりやすいですね。タイムアウトです。
既に想像に難くないです。コルーチンのタイムアウトでは無いかと推察しておきます。

それでは、読んでみましょう

> The most obvious reason to cancel coroutine execution in practice is because its execution time has exceeded some timeout. While you can manually track the reference to the corresponding Job and launch a separate coroutine to cancel the tracked one after delay, there is a ready to use withTimeout function that does it. Look at the following example:

意訳込みですが、以下のとおりです。

> キャンセルする理由として最も明確なものしては、タイムアウトがあります。
> もちろんJobオブジェクトを取得して、時間を計測してキャンセルする方法が思い浮かぶと思いますが、最も簡単なものとしては、withTimeout関数を利用することです。サンプルコードをみてみましょう。

確かに、キャンセルするとしたら時間を計測すると最初は考えるかもしれませんね。
でも、便利な関数があるのであれば利用したいところです。
サンプルコードを確認してみます。

```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    withTimeout(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
}
```
そして実行結果を確認してみます。

```text
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms
	at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:122)
	at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:88)
	at kotlinx.coroutines.EventLoopBase$DelayedRunnableTask.run(EventLoop.kt:316)
	at kotlinx.coroutines.EventLoopBase.processNextEvent(EventLoop.kt:123)
	at kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:61)
	at java.base/java.lang.Thread.run(Thread.java:834)
```

例外が発生しています。 **TimeoutCancellationException** 例外のようです。
名前からしてわかりやすいです。タイムアウトでキャンセル例外！！
名は体を表すです。

withTimeout関数の実装を確認してみます。

```kotlin
public suspend fun <T> withTimeout(timeMillis: Long, block: suspend CoroutineScope.() -> T): T {
    if (timeMillis <= 0L) throw CancellationException("Timed out immediately")
    return suspendCoroutineUninterceptedOrReturn { uCont ->
        setupTimeout(TimeoutCoroutine(timeMillis, uCont), block)
    }
}
```
 引数はミリ秒を指定するみたいです。

少し気になることがあるのでこんなふうに変更をしてみました。

```kotlin
fun main() = runBlocking {
    withTimeout(1300L) {
        withContext(NonCancellable) {
            repeat(5) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        }
    }
}
```

 withContext(NonCancellable) を利用すればキャンセルさせないよことができるはずです。
しかし、タイムアウトと併用するとどうなるのかを試してみます。
繰り返し回数は5回に減らしています。

結果をみてみましょう。

```text
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
I'm sleeping 3 ...
I'm sleeping 4 ...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms
	at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:122)
	at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:88)
	at kotlinx.coroutines.EventLoopBase$DelayedRunnableTask.run(EventLoop.kt:316)
	at kotlinx.coroutines.EventLoopBase.processNextEvent(EventLoop.kt:123)
	at kotlinx.coroutines.DefaultExecutor.run(DefaultExecutor.kt:61)
	at java.base/java.lang.Thread.run(Thread.java:834)
```

おぉ！しっかりキャンセルをされずに処理が完遂している！
しかし、処理後に例外が発生しています。使う際には気をつけなければですね。

脱線しましたが、続きを読み進めます。

> The TimeoutCancellationException that is thrown by withTimeout is a subclass of CancellationException. We have not seen its stack trace printed on the console before. That is because inside a cancelled coroutine CancellationException is considered to be a normal reason for coroutine completion. However, in this example we have used withTimeout right inside the main function.

意訳込みですが（略）

>  **TimeoutCancellationException** は **CancellationException** のサブクラスです。
> cencel関数を呼び出したときには、スタックトレースが表示されませんでしたが、今回はスタックトレースが表示されています。
>  **CancellationException** は明示的にキャンセルを行っているため、正常終了という扱いになります。
> **TimeoutCancellationException**は正常ではなく、強制的に終了させているため、例外の扱いです。

1つ疑問に思っていた事が解消されました。そのような理由からスタックトレースが表示されていたんですね。

更に読み進めます。

> Because cancellation is just an exception, all the resources are closed in a usual way. You can wrap the code with timeout in try {...} catch (e: TimeoutCancellationException) {...} block if you need to do some additional action specifically on any kind of timeout or use withTimeoutOrNull function that is similar to withTimeout, but returns null on timeout instead of throwing an exception:

訳します

> キャンセルは例外扱いなので、リソースの開放などは、これまでとおりtry-catchを利用していきます。
> もし、特別な処理を行いたい場合は、withTimeoutOrNull関数が便利です。withTimeoutと似ていますが、nullなどを返すことが可能です。サンプルコードをみてください。


 **withTimeoutOrNull** 関数が便利だそうです。サンプルコードもあるようなので確認してみます。

```kotlin
fun main() = runBlocking {
    val result = withTimeoutOrNull(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
        "Done" // will get cancelled before it produces this result
    }
    println("Result is $result")
}
```

戻り値をresultで受け取ってます。
実行結果も確認してみます。

```text
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Result is null
```

例外が発生せず、たしかにnullが返ってきてます。
もし、タイムアウトが発生しない場合は、どうなるのか気になるのでコードを変更してみました。

```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val result = withTimeoutOrNull(1300L) {
        repeat(1) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
        "Done" // will get cancelled before it produces this result
    }
    println("Result is $result")
}
```

繰り返し処理を1回にしてみました。
実行結果は以下のとおりです。

```kotlin
I'm sleeping 0 ...
Result is Done
```

キャンセルされなければ、 **withTimeoutOrNull** 関数ないの最後の行の処理結果が返されています。ただ、Nullableな変数であることには気をつけなければですね。

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

* withTimeout関数でタイムアウトを発生できるがTimeoutCancellationException例外が発生する
* 例外処理は通常通りおこなう
* withTimeoutOrNull関数を利用すれば、タイムアウトが発生した時に任意の値を返せる
* withContext(NonCancellable)関数を利用すれば、タイムアウトキャンセルを待ってもらえる