コルーチンでの例外処理の基礎
こんにちは、今回はコルーチン内で生じる例外処理の基礎についてまとめました。
コルーチンとは?に関しては公式ドキュメントを参照して、以前まとめているのでこちらを参照ください。
また、今回も公式ドキュメントを参考に学習した内容なので、詳しくは以下のURLで学習してみてください。
例外の概要
例外とは、コードの実行中に発生する予期しないイベントです。
fun main() {
val x = 0
val y = 2
println(y / x) //0で割ることはできない
}
このプログラムを実行すると、値をゼロで割ることはできないため、プログラムは計算例外でクラッシュします。
コルーチン上での例外
import kotlinx.coroutines.*
fun main() {
runBlocking {
println("Weather forecast")
println(getWeatherReport())
println("Have a good day!")
}
}
suspend fun getWeatherReport() = coroutineScope { //親コルーチン
val forecast = async { getForecast() }
val temperature = async { getTemperature() }
"${forecast.await()} ${temperature.await()}"
}
suspend fun getForecast(): String { //子コルーチン
delay(1000)
return "Sunny"
}
suspend fun getTemperature(): String { //子コルーチン
delay(500)
throw AssertionError("Temperature is invalid")//エラーを意図的に発生させる
return "30\u00b0C"
}
このプログラムは、天気と気温を出力するコルーチンサンプルプログラムですが、getTemperature()
で意図的にエラーを投げてみます。
- throw:意図的にエラーを投げるキーワード
Weather forecast
Exception in thread "main" java.lang.AssertionError: Temperature is invalid
at FileKt.getTemperature (File.kt:24)
at FileKt$getTemperature$1.invokeSuspend (File.kt:-1)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
出力結果は上記のようになります。
何が起きているのかというと、親と子の関係を把握することでイメージができます。
ここでいう親とはgetWeatherReport()
であり、中にあるgetForecast()
とgetTemperature()
が子に当たります。
子コルーチンのどちらか一つでも例外を吐いてしまうと親コルーチン全体で処理が止まってしまうのです。
プログラムが複雑になるにつれて、例外は必然的に出てくるので、毎回止まってしまっては話になりません。
なので、例外処理をする必要があります。
try-catchによる例外処理
- try-catch:エラーが起こる可能性のある処理を安全に実行し、例外発生時に適切に対処するための構文です。
try {
// エラーが起きうるプログラム
} catch (e: IllegalArgumentException) {
// そこでエラーが生じたときの適切な処理
}
try-catchを用いて先ほど生じたエラーの対処方法を2つ紹介します。
①main()
関数内のprintln(getWeatherReport())
で例外処理をする
② suspend関数getWeatherReport()
内のasync { getTemperature() }
に例外処理をする
順番に説明していきます。
try-catch①
import kotlinx.coroutines.*
fun main() {
runBlocking {
println("Weather forecast")
try {
println(getWeatherReport()) //気温を出力時にエラーが発生するはず
} catch (e: AssertionError) {
println("Caught exception in runBlocking(): $e")
println("Report unavailable at this time")
}
println("Have a good day!")
}
}
suspend fun getWeatherReport() = coroutineScope {
val forecast = async { getForecast() }
val temperature = async { getTemperature() }
"${forecast.await()} ${temperature.await()}"
}
suspend fun getForecast(): String {
delay(1000)
return "Sunny"
}
suspend fun getTemperature(): String {
delay(500)
throw AssertionError("Temperature is invalid") //意図的にエラーを発生
return "30\u00b0C"
}
- e:発生した例外オブジェクト(エラー情報)を受け取るための変数(eは慣例で使われるが、任意の変数名でOK)
この対処法は比較的わかりやすいと思います。main()
内で明らかにprintln(getWeatherReport())
でエラーが発生することは直感的にわかると思います。そこにtry-catchで囲い、生じたエラー情報を変数eに代入(このときの型は AssertionError クラス)し、どういったエラーが発生したかを表示する処理をします。
Weather forecast
Caught exception in runBlocking(): java.lang.AssertionError: Temperature is invalid
Report unavailable at this time
Have a good day!
例外処理により、親コルーチンすべてが止まることなく、Have a good day!
まで出力ができています。
try-catch②
次は①よりも本質に迫った例外処理になります。
そもそも何が原因でエラーになっているのかを考えると、getTemperature
でエラーが発生しています。
つまり、getForecast()
には問題はなく、getWeatherReport()
全体に例外処理をする必要がないということです。
import kotlinx.coroutines.*
fun main() {
runBlocking {
println("Weather forecast")
println(getWeatherReport())
println("Have a good day!")
}
}
suspend fun getWeatherReport() = coroutineScope {
val forecast = async { getForecast() } //問題なし
val temperature = async { //ここにだけ例外処理を加える
try {
getTemperature()
} catch (e: AssertionError) {
println("Caught exception $e")
"{ No temperature found }"
}
}
"${forecast.await()} ${temperature.await()}"
}
suspend fun getForecast(): String {
delay(1000)
return "Sunny"
}
suspend fun getTemperature(): String {
delay(500)
throw AssertionError("Temperature is invalid") //エラーを意図的に発生
return "30\u00b0C"
}
このプログラムを実行すると、
Weather forecast
Caught exception java.lang.AssertionError: Temperature is invalid
Sunny { No temperature found } //Sunnyは問題なく出力される
Have a good day!
この様にSunny
だけは無事に出力され、気温の出力のみ例外処理が行われています。
最後に
今回は公式ドキュメントのコードを引用し、try-catchの基礎を学習しました。
- コルーチンでは、子の例外が親に伝播することを理解するのが重要。
-
try-catch
はエラー処理の基本だが、使いどころを見極めると柔軟な設計ができる。
今後は、コルーチンコンセプトについて学んでいきたいと思います。