【Coroutinesガイド】例外ハンドリング

※ソース記事は[こちら](https://kotlinlang.org/docs/exception-handling.html)
このセクションでは、例外ハンドリングと例外時のキャンセルについて取り扱う。キャンセルされたコルーチンは、suspendの時点で、[CancellationException](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html)をスローし、それはコルーチンの仕組みにより無視されていることをすでに知っている。ここでは、例外がキャンセルの間にスローされたり、同じコルーチンの複数の子が例外をスローする場合、何が起こるかを見ていく。
# 例外の伝播
コルーチンビルダーは二つの特徴に分けられる。例外を自動で伝播する([launch](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html)と[actor](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html))か、ユーザーに晒す([async](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html)と[produce](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html))かである。これらのビルダーがルートコルーチンを作るために使われると、それは他のコルーチンの子ではないので、前者のビルダーは、Javaの`Thread.uncaughtExceptionHandler`に似た**uncaught**例外として扱い、一方後者は、最終的な例外を、例えば[await](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html)や[receive](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html)([produce]()と[receive]()は[チャネル](#チャネル)セクションで扱う)経由で、consumeするかどうかはユーザー次第である。
[GlobalScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html)を使ってルートコルーチンを作る簡単な例により、デモンストレーションすることができる。

::: note
[GlobalScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html)は、手に負えない方向に裏目に出ることがある繊細なAPIである。アプリケーション全体のためにルートコルーチンを作ることは、`GlobalScope`の滅多にない合理的な使用の一つであるため、`GlobalScope`の使用を`@OptIn(DelicateCoroutinesApi::class)`で明示的にオプトインしなければならない。
:::
```Kotlin
import kotlinx.coroutines.*

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val job = GlobalScope.launch { // launchを使うルートコルーチン
        println("Throwing exception from launch")
        throw IndexOutOfBoundsException() // Thread.defaultUncaughtExceptionHandlerによってコンソール出力される
    }
    job.join()
    println("Joined failed job")
    val deferred = GlobalScope.async { // asyncを使うルートコルーチン
        println("Throwing exception from async")
        throw ArithmeticException() // 何も出力されない。awaitを呼ぶことはユーザー次第である
   }
    try {
        deferred.await()
        println("Unreached")
    } catch (e: ArithmeticException) {
        println("Caught ArithmeticException")
    }
}
```
このコードの出力は次のようになる。([debug](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/coroutine-context-and-dispatchers.md#debugging-coroutines-and-threads)モードで)
```
Throwing exception from launch
Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.IndexOutOfBoundsException
Joined failed job
Throwing exception from async
Caught ArithmeticException
```
# CoroutineExceptionHandler﻿
コンソールに`uncaught`例外を出力するデフォルトのふるまいをカスタマイズすることは可能である。ルートコルーチンの[CoroutineExceptionHandler](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html)コンテキスト要素を、カスタム例外ハンドリングが起こるかもしれないルートコルーチンとそのすべての子のための、一般的な`catch`ブロックとして使うことができる。それは[Thread.uncaughtExceptionHandler](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler))に似ている。`CoroutineExceptionHandler﻿`内の例外からは回復することはできない。ハンドラーが呼び出されるとき、コルーチンはすでに対応する例外で完了している。通常、ハンドラーは例外をログ出力したり、いくつかの種類のエラーメッセージを表示したり、終了したり、アプリケーションを再起動したりするために使われる。
`CoroutineExceptionHandler﻿`は`uncaught`例外、つまり他の方法ではハンドルされなかった例外、においてのみ呼び出される。特に、すべての子のコルーチン(別の[Job](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html)のコンテキストで作られたコルーチン)は、その例外ハンドリングを親のコルーチンに移譲し、それも親に移譲し、ルートまでそれが続くため、それらのコンテキストに設置された`CoroutineExceptionHandler`は決して使われない。さらに、[async](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html)ビルダーは、常にすべての例外をキャッチし、結果として生じる[Deferred](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html)オブジェクト内でそれらを表すため、その`CoroutineExceptionHandler`も効果が無い。

::: note
監督スコープで動作するコルーチンは、親へ例外を伝播せず、このルールから除外されている。このドキュメントのもっと先にある[監視](#監視)セクションで、より詳細が提供されている。
:::
```Kotlin
import kotlinx.coroutines.*

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception -> 
        println("CoroutineExceptionHandler got $exception") 
    }
    val job = GlobalScope.launch(handler) { // GlobalScopeで動作するルートコルーチン
        throw AssertionError()
    }
    val deferred = GlobalScope.async(handler) { // これもルートだが、launchでなくasync
        throw ArithmeticException() // 何も出力されない。deferred.await()を呼ぶことはユーザー次第である
    }
    joinAll(job, deferred)    
}
```
このコードの出力は以下の通り。
```
CoroutineExceptionHandler got java.lang.AssertionError
```
# キャンセルと例外
キャンセルは例外と密接に関連している。コルーチンはキャンセルのために、内部で`CancellationException`を使用しており、これらの例外はすべてのハンドラで無視されるため、`catch`ブロックで得られる追加のデバッグ情報の元としてのみ、使うべきである。コルーチンが、[Job.cancel](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html)を使ってキャンセルされるとき、それは終了するが、その親はキャンセルされない。
```Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        val child = launch {
            try {
                delay(Long.MAX_VALUE)
            } finally {
                println("Child is cancelled")
            }
        }
        yield()
        println("Cancelling child")
        child.cancel()
        child.join()
        yield()
        println("Parent is not cancelled")
    }
    job.join()    
}
```
このコードの出力は以下のとおり。
```
Cancelling child
Child is cancelled
Parent is not cancelled
```
コルーチンが`CancellationException`以外の例外に出くわした場合、親をその例外でキャンセルする。このふるまいは、上書きすることはできず、[構造化並列性](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/composing-suspending-functions.md#structured-concurrency-with-async)のために安定したコルーチンの階層を提供するために使われている。[CoroutineExceptionHandler](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html)の実装は、子のコルーチンのためには使われていない。

::: note 
これらの例では、[CoroutineExceptionHandler](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html)は常に[GlobalScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html)で作られたコルーチンに設置されている。メインの[runBlocking](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html)スコープで起動したコルーチンに対して例外ハンドラを設置するのは意味がない。なぜなら、メインコルーチンは、設置された例外ハンドラに関わらず、子が例外で完了するときは、常にキャンセルしようとするからである。
:::
もともとの例外は、すべての子が終了したときに親によってのみハンドルされ、それは、次の例によって実証される。
```Kotlin
import kotlinx.coroutines.*

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception -> 
        println("CoroutineExceptionHandler got $exception") 
    }
    val job = GlobalScope.launch(handler) {
        launch { // 最初の子
            try {
                delay(Long.MAX_VALUE)
            } finally {
                withContext(NonCancellable) {
                    println("Children are cancelled, but exception is not handled until all children terminate")
                    delay(100)
                    println("The first child finished its non cancellable block")
                }
            }
        }
        launch { // 二番目の子
            delay(10)
            println("Second child throws an exception")
            throw ArithmeticException()
        }
    }
    job.join() 
}
```
このコードは次のように出力する。
```
Second child throws an exception
Children are cancelled, but exception is not handled until all children terminate
The first child finished its non cancellable block
CoroutineExceptionHandler got java.lang.ArithmeticException
```
# 例外の集約
コルーチンの複数の子が例外で失敗するとき、一般的なルールは、"最初の例外が勝つ"であるため、最初の例外はハンドリングされる。最初の例外の後で発生したすべての追加の例外は、抑制された例外として、最初の例外に付属される。
```Kotlin
import kotlinx.coroutines.*
import java.io.*

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}")
    }
    val job = GlobalScope.launch(handler) {
        launch {
            try {
                delay(Long.MAX_VALUE) // これは他の兄弟がIOExceptionで失敗するとき、キャンセルされる
            } finally {
                throw ArithmeticException() // 二番目の例外
            }
        }
        launch {
            delay(100)
            throw IOException() // 最初の例外
        }
        delay(Long.MAX_VALUE)
    }
    job.join()  
}
```

::: note
この上記のコードは、`suppressed`例外をサポートするJDK7以上においてのみ、適切に動作するだろう。
:::

このコードの出力は次のとおり。
```
CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]
```
::: note
このメカニズムは現在Javaバージョン7以上でのみ、動作する。JSとNativeの制限は一時的であり、将来改善するだろう。
:::
Cancellation例外は、透過的であり、デフォルトでラップされない。
```Kotlin
import kotlinx.coroutines.*
import java.io.*

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception")
    }
    val job = GlobalScope.launch(handler) {
        val inner = launch { // このすべてのコルーチンの山は、キャンセルされる            
            launch {
                launch {
                    throw IOException() // もともとの例外
                }
            }
        }
        try {
            inner.join()
        } catch (e: CancellationException) {
            println("Rethrowing CancellationException with original cause")
            throw e // cancellation例外がリスローされるが、もともとのIO例外がハンドラーに到達する  
        }
    }
    job.join()    
}
```
このコードの出力は次のとおり。
```
Rethrowing CancellationException with original cause
CoroutineExceptionHandler got java.io.IOException
```
# 監視
以前学んだように、キャンセルはコルーチン階層全体を通じて伝播する、双方向の関係である。単一方向のキャンセルが必要な場合についてみていこう。
そのような必要性の良い例は、スコープに定義されたjobを持つ、UIコンポーネントである。何らかのUIの子タスクが失敗したときは、必ずしも常にUIコンポーネント全体をキャンセル(効果的にkillする)必要はなく、UIコンポーネントが破棄される(そしてそのジョブがキャンセルされる)とき、結果は最早必要ないのため、すべての子ジョブはキャンセルする必要がある。
別の例は、複数の子ジョブを起動するサーバープロセスであり、それらの実行を監視する必要があり、それらの失敗を追跡し、失敗したもののみ再起動するだけ、というものがある。
## 監視ジョブ
[SupervisorJob](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html)をこれらの目的に使うことができる。キャンセルが下流にのみ伝播する唯一の例外を持つ、通常の[Job](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job.html)に似ている。これは次の例を使って、簡単に実証することができる。
```Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    with(CoroutineScope(coroutineContext + supervisor)) {
        // 最初の子の起動 -- この例では例外は無視される。(実際にはしないこと!)
        val firstChild = launch(CoroutineExceptionHandler { _, _ ->  }) {
            println("The first child is failing")
            throw AssertionError("The first child is cancelled")
        }
        // 二つ目の子の起動
        val secondChild = launch {
            firstChild.join()
            // 最初の子のキャンセルは二つ目の子には伝播しない。
            println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
            try {
                delay(Long.MAX_VALUE)
            } finally {
                // しかしsupervisorのキャンセルは伝播する。
                println("The second child is cancelled because the supervisor was cancelled")
            }
        }
        // 最初の子が失敗し完了するまで待機する
        firstChild.join()
        println("Cancelling the supervisor")
        supervisor.cancel()
        secondChild.join()
    }
}
```
このコードの出力は以下の通り。
```
The first child is failing
The first child is cancelled: true, but the second one is still active
Cancelling the supervisor
The second child is cancelled because the supervisor was cancelled
```
## 監視スコープ
[coroutineScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html)の代わりに、スコープ付き並列性のために、[supervisorScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html)を使うことができる。それは自分自身が失敗する場合、一方向にキャンセルを伝播し、自分のすべての子のみをキャンセルする。それはちょうど[coroutineScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html)のように、すべての子の完了も待つ。
```Kotlin
import kotlin.coroutines.*
import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        supervisorScope {
            val child = launch {
                try {
                    println("The child is sleeping")
                    delay(Long.MAX_VALUE)
                } finally {
                    println("The child is cancelled")
                }
            }
            // yieldを使って子に実行と出力の機会を与える
            yield()
            println("Throwing an exception from the scope")
            throw AssertionError()
        }
    } catch(e: AssertionError) {
        println("Caught an assertion error")
    }
}
```
このコードの出力は以下の通り。
```
The child is sleeping
Throwing an exception from the scope
The child is cancelled
Caught an assertion error
```
## 監視スコープでの例外
通常と監視ジョブのもう一つの重要な違いは、例外ハンドリングである。それぞれの子は例外ハンドリングのメカニズム経由で、自分自身で例外をハンドリングすべきである。この違いは、子の失敗が親に伝播しないという事実から来る。それは、[supervisorScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html)の内側で直接起動されたコルーチンは、ルートコルーチンがするのと同じ方法で、自分のスコープに設置された[CoroutineExceptionHandler](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html)を使うことを意味する。(詳細は[CoroutineExceptionHandler]( #coroutineexceptionhandler)のセクションを参照のこと)
```Kotlin
import kotlin.coroutines.*
import kotlinx.coroutines.*

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception -> 
        println("CoroutineExceptionHandler got $exception") 
    }
    supervisorScope {
        val child = launch(handler) {
            println("The child throws an exception")
            throw AssertionError()
        }
        println("The scope is completing")
    }
    println("The scope is completed")
}
```
このコードの出力は以下のとおり。
```
The scope is completing
The child throws an exception
CoroutineExceptionHandler got java.lang.AssertionError
The scope is completed
```