検証環境
この記事の内容は、以下の環境で検証しました。
- Intellij IDEA ULTIMATE 2018.2
- Kotlin 1.3.0
- Gradle Projectで作成
- GradleファイルはKotlinで記述(KotlinでDSL)
準備
詳細は下記の準備を参照してください。
https://qiita.com/naoi/items/8abf2cddfc2cb3802daa
Run non-cancellable block
今回のタイトルは「キャンセルさせない」です。これまではキャンセルさせることに注力していましたが、今回はキャンセルさせないってことでしょうか。
早速、読んでいきます。
Any attempt to use a suspending function in the finally block of the previous example causes CancellationException, because the coroutine running this code is cancelled. Usually, this is not a problem, since all well-behaving closing operations (closing a file, cancelling a job, or closing any kind of a communication channel) are usually non-blocking and do not involve any suspending functions. However, in the rare case when you need to suspend in the cancelled coroutine you can wrap the corresponding code in withContext(NonCancellable) {...} using withContext function and NonCancellable context as the following example shows:
意訳込みですが、訳してみると以下のようになります。
サスペンド関数実行中にキャンセルするとCancellationExceptionが発生します。これはfinally句でも同じです。finally句でよく記述するリソースの開放などは非ブロッキング関数なので、例外は発生しません。しかし、たまにですが、finally句でサスペンド関数を呼び出すことがあります。そのようなケースでは、例外が発生し、finally句の処理が途中で中断されてしまいます。回避する方法として、withContext関数とNonCancellableを使用することで一時的に非ブロッキングにできます。詳細は、サンプルコードをみてください。
確かに、finally句でサスペンド関数を呼ぶとキャンセルされそうですね。それも回避できるのは嬉しいです。早速、サンプルコードを確認してみます。
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
} finally {
withContext(NonCancellable) {
println("I'm running finally")
delay(1000L)
println("And I've just delayed for 1 sec because I'm non-cancellable")
}
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
finally句にwithContext関数が呼び出されていますね。
あと、注目する点としては、withContext(NonCancellable) 関数内でdelay関数が呼び出されています。
delay関数はサスペンド関数なので、通常だったらキャンセルされて、「"And I've just delayed for 1 sec because I'm non-cancellable"」は表示されない。しかし、今回は表示されるはず!
実行してみました。
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
I'm running finally
And I've just delayed for 1 sec because I'm non-cancellable
main: Now I can quit.
逆に、withContext(NonCancellable) 関数で囲まないとどうなるのか気になりますね。
サンプルコードを以下のように修正してみました。
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
} finally {
println("I'm running finally")
delay(1000L)
println("And I've just delayed for 1 sec because I'm non-cancellable")
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
実行結果を確認してみると、以下のようになりました。
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
I'm running finally
main: Now I can quit.
確かに、「And I've just delayed for 1 sec because I'm non-cancellable」が表示されていない。
delay関数がキャンセルされたと考えられます。
まとめ
このブロックで理解できたことは以下のことだと思います。
- サスペンド関数で キャンセルを一時的に無効にすることができる
- withContext(NonCancellable) 内は一時的にキャンセルを無効にできる