技術評論社 WEB+DB PRESS Vol. 109 特集2 “[実践] Kotlin” (第2, 3, 5章) を執筆しました。
続きを読む 技術評論社 WEB+DB PRESS Vol. 109 特集2 を執筆しました「[:en]Coroutine[:ja]コルーチン[:]」タグアーカイブ
Kotlin 1.3: コルーチンの監督(スーパバイザ)
Kotlin 1.3: コルーチンでの例外の扱い方 (1/2)
Kotlin 1.3 で使えるようになる コルーチンでの例外の扱いについてドキュメントを読んでまとめました。
ここでは例外処理と例外発生時のキャンセル処理について記述しています。 キャンセルされたコルーチンは CancellationException
を停止位置でスローします。 そして、コルーチンの機構によりそれは無視されます。 しかし、 例外がキャンセルされたり、 同じコルーチンの複数の子が例外をスローした場合はどうなるでしょうか。
検証環境
- OpenJDK 10.0.2
- Kotlin 1.3.0-rc-146
- org.jetbrains.kotlinx:kotlinx-coroutines-core:0.30.2
例外の伝播
コルーチンビルダには2つの種類があります。 例外を自動的に伝播するもの (launch
, actor
) と ユーザに例外処理を委ねるもの (async
, produce
)。 前者は Java の Thread.uncaughtExceptionHandler
のように、 例外を未処理のものとして扱います。 後者はユーザが最終的な例外をどのように処理するかに依存しており、 例えば await
, receive
があります。
GlobalScope
を使った単純な例で実験してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
fun main(args: Array<String>) = runBlocking { val job = GlobalScope.launch { println("Throwing exception from launch") throw IndexOutOfBoundsException() // Will be printed to the console by Thread.defaultUncaughtExceptionHandler } job.join() println("Joined failed job") val deferred = GlobalScope.async { println("Throwing exception from async") throw ArithmeticException() // Nothing is printed, relying on user to call await } try { deferred.await() println("Unreached") } catch (e: ArithmeticException) { println("Caught ArithmeticException") } } |
出力は次のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Throwing exception from launch Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.IndexOutOfBoundsException at com.improve_future.kotlincoroutine.MainKt$main$1$job$1.invokeSuspend(Main.kt:44) at com.improve_future.kotlincoroutine.MainKt$main$1$job$1.invoke(Main.kt) at kotlin.coroutines.experimental.migration.ExperimentalSuspendFunction1Migration.invoke(CoroutinesMigration.kt:134) at kotlin.coroutines.experimental.migration.ExperimentalSuspendFunction1Migration.invoke(CoroutinesMigration.kt:130) at kotlin.coroutines.experimental.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnchecked$$inlined$buildContinuationByInvokeCall$IntrinsicsKt__IntrinsicsJvmKt$2.resume(IntrinsicsJvm.kt:122) at kotlin.coroutines.experimental.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnchecked$$inlined$buildContinuationByInvokeCall$IntrinsicsKt__IntrinsicsJvmKt$2.resume(IntrinsicsJvm.kt:98) at kotlinx.coroutines.experimental.DispatchedTask$DefaultImpls.run(Dispatched.kt:168) at kotlinx.coroutines.experimental.DispatchedContinuation.run(Dispatched.kt:13) at kotlinx.coroutines.experimental.scheduling.Task.run(Tasks.kt:94) at kotlinx.coroutines.experimental.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:583) at kotlinx.coroutines.experimental.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60) at kotlinx.coroutines.experimental.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:729) Joined failed job Throwing exception from async Caught ArithmeticException |
launch
はその場で例外が発生しており、 await
は処理実行時に例外を発生させています。
CoroutineExceptionHandler
もしユーザが例外の全てをコンソールに表示したくない場合はどうでしょうか。 CoroutineExceptionHandler
コンテキスト が、 コルーチンの一般的な catch
ブロックに使用することができ、 独自のログ出力や例外処理を行う際に使用できます。 これは Thread.uncaughtExceptionHandler
に似ています。
JVM では ServiceLoader
を経由して CoroutineExceptionHander
を登録することで、 全てのコルーチンに対して グローバル例外ハンドラ を再定義することができます。 グローバル例外ハンドラは、 他に特別なハンドラが登録されていない場合の Thread.defaultUncaughtExceptionHandler
に似ています。 Android では uncaughtExceptionPreHandler
がグローバルコルーチン例外ハンドラとして登録されています。
CoroutineExceptionHandler
は ユーザが予期していなかった例外が発生した場合にのみ実行されます。 そのため async
のビルダなどに登録しても効果がありません。
1 2 3 4 5 6 7 8 9 10 11 12 |
fun main(args: Array<String>) = runBlocking { val handler = CoroutineExceptionHandler { _, exception -> println("Caught $exception") } val job = GlobalScope.launch(handler) { throw AssertionError() } val deferred = GlobalScope.async(handler) { throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await() } joinAll(job, deferred) } |
このコードのアウトプットは次のようになります。
1 |
Caught java.lang.AssertionError |
launch
でも、 async
でも CoroutineExceptionHandler
を使っていますが、 async
の方では意味をなしていません。 コードが実行されていないためです。
次のようにコードを変えてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
fun main(args: Array<String>) = runBlocking { val handler = CoroutineExceptionHandler { _, exception -> println("Caught $exception") } val job = GlobalScope.launch(handler) { throw AssertionError() } val deferred = GlobalScope.async(handler) { throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await() } joinAll(job, deferred) deferred.await() delay(1000L) } |
出力が変わってきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Caught java.lang.AssertionError Exception in thread "main" java.lang.ArithmeticException at com.improve_future.kotlincoroutine.MainKt$main$1$deferred$1.invokeSuspend(Main.kt:48) at com.improve_future.kotlincoroutine.MainKt$main$1$deferred$1.invoke(Main.kt) at kotlin.coroutines.experimental.migration.ExperimentalSuspendFunction1Migration.invoke(CoroutinesMigration.kt:134) at kotlin.coroutines.experimental.migration.ExperimentalSuspendFunction1Migration.invoke(CoroutinesMigration.kt:130) at kotlin.coroutines.experimental.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnchecked$$inlined$buildContinuationByInvokeCall$IntrinsicsKt__IntrinsicsJvmKt$2.resume(IntrinsicsJvm.kt:122) at kotlin.coroutines.experimental.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnchecked$$inlined$buildContinuationByInvokeCall$IntrinsicsKt__IntrinsicsJvmKt$2.resume(IntrinsicsJvm.kt:98) at kotlinx.coroutines.experimental.DispatchedTask$DefaultImpls.run(Dispatched.kt:168) at kotlinx.coroutines.experimental.DispatchedContinuation.run(Dispatched.kt:13) at kotlinx.coroutines.experimental.scheduling.Task.run(Tasks.kt:94) at kotlinx.coroutines.experimental.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:583) at kotlinx.coroutines.experimental.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60) at kotlinx.coroutines.experimental.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:729) |
出力は変わったものの、 async
で登録した CoroutineExceptionHandler
は実行されていません。
async
の中で launch
を実行しても、 スタックトレースは変わるものの、同じ結果になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
fun main(args: Array<String>) = runBlocking { val handler = CoroutineExceptionHandler { _, exception -> println("Caught $exception") } val job = GlobalScope.launch(handler) { throw AssertionError() } val deferred = GlobalScope.async(handler) { launch(handler) { throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await() } } joinAll(job, deferred) deferred.await() delay(1000L) } |
Kotlin 1.3: コルーチンでの例外の扱い方 (2/2) に続きます。
Kotlin 1.3: コルーチン
Kotlin 1.3 で使えるようになるコルーチンについてドキュメントを読んでまとめました。 基本的に公式ドキュメントの訳ですが、 ところどころコードを変更して実行した実験結果など追加しています。
続きを読む Kotlin 1.3: コルーチン