Coroutineのキャンセルとタイムアウト
コルーチンの実行をキャンセル
Cancelling coroutine execution
import kotlinx.coroutines.* fun main() = runBlocking { val job = launch { repeat(1000) { i -> println("job: I'm sleeping $i ...") delay(500L) } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancel() // コルーチンのキャンセル job.join() // コルーチンの完了を待つ println("main: Now I can quit.") } /* job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! main: Now I can quit. */
コルーチンのキャンセルについては、cancelAndJoin
関数も用意されている。
コルーチンキャンセルは協同する
Cancellataion is cooperative
kotlinx.coroutines
に登録されているサスペンド関数は全てキャンセルすることができる。
コルーチンがキャンセルされると、CancellationExceptionがthrowされる。
しかしながら、実行中のコルーチンはキャンセルすることはできない。
そこでキャンセルのあとでjoin()
することによって完了を待機する。
import kotlinx.coroutines.* fun main() = runBlocking { val startTime = System.currentTimeMillis() val job = launch(Dispatchers.Default) { var nextPrintTime = startTime var i = 0 while (i < 5) { // 計算ループ if (System.currentTimeMillis() >= nextPrintTime) { println("job: I'm sleeping ${i++} ...") nextPrintTime += 500L } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancelAndJoin() // キャンセルと完了待ち println("main: Now I can quit.") } /* job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! job: I'm sleeping 3 ... job: I'm sleeping 4 ... main: Now I can quit. */
最後のjoinがなければ、待機しない
val job = launch(Dispatchers.Default) { var nextPrintTime = startTime var i = 0 while (i < 5) { // 計算ループ if (System.currentTimeMillis() >= nextPrintTime) { println("job: I'm sleeping ${i++} ...") nextPrintTime += 500L } } } delay(1300L) // delay a bit println("main: I'm tired of waiting!") job.cancel() // AndJoin() キャンセルのみにすると println("main: Now I can quit.") /* 完了を待機しなければ job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! main: Now I can quit. job: I'm sleeping 3 ... job: I'm sleeping 4 ... */
計算コードをキャンセルできるようにする
yield
関数を使って、キャンセルされたかどうかを確認するサスペンド関数を用意する- ループする際に、キャンセルされていないかどうか確認するようにする
// while (i < 5) { while (isActive) { // cancellable computation loop // print a message twice a second if (System.currentTimeMillis() >= nextPrintTime) { println("job: I'm sleeping ${i++} ...") nextPrintTime += 500L } }
finnalyを使ってリソースを閉じる
キャンセルができるサスペンド関数は、キャンセル時に、CancellationExceptionをthrowするので、ハンドルすることができる。
例として、try {...} finally {...}
式とKotlinのuse
関数を使う。
val job = launch { try { repeat(1000) { i -> println("job: I'm sleeping $i ...") delay(500L) } } finally { // キャンセルもしくは、try内部完了後に呼び出される println("job: I'm runnning finally") } } delay(1300L) // yeildされる println("main: I'm tired of waiting!") job.cancelAndJoint() // キャンセルと完了を待機する println("main: Now I can quit.") /* job: I'm sleeping 0 ... job: I'm sleeping 1 ... job: I'm sleeping 2 ... main: I'm tired of waiting! job: I'm running finally main: Now I can quit. */
キャンセルできないブロックを実行する
コルーチンが一度キャンセルされると、内部でサスペンド関数は全てキャンセルされ、実行することができないが、
サスペンド関数 withContext(NonCancellable) {...}
を使うことで、finallyの後でもサスペンド関数を呼び出すことができる。
val job = launch { try { repeat(1000) { i -> println("job: I'm sleeping $i ...") delay(500L) } } finally { // キャンセル後に withContext(NonCancellable) { println("job: I'm running finally") delay(1000L) println("job: And I've just delayed for 1 sec because I'm non-cancellable") } } }
Timeout
withTimeout
サスペンド関数は、タイムアウトを設定することができる。
fun main() = runBlocking { launch { try { // 5Sでタイムアウトし、キャンセルされ、例外がスローされる withTimeout(5000L) { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } } catch(e: TimeoutCancellationException) { // CancellationExceptionのサブクラス // タイムアウト時の処理 } } println("main end") }
キャンセルはただの例外であるため、CancellationExceptionをハンドリングしなくても全てのリソースは通常通り閉じられる。
fun main() = runBlocking { launch { // 5Sでタイムアウトし、キャンセルされ、例外がスローされる withTimeout(5000L) { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } } println("main end") }