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") }
コルーチンの基本
コルーチンとは
About Coroutines
Kotlinの非同期関数。 スレッドと違い、何千個作成してもCPUの負荷を増大させない
最初のコルーチン
Coroutineは、CoroutinScope内部で実行し、CoroutineScopeは、実行されたCoroutineが全て完了するまで、自身を終了させない。
// kotlinxをインポートする import kotlinx.coroutines.* fun main() = runBlocking { // this: CoroutineScope launch { // Coroutineの生成 (async) delay(5000L) // サスペンド関数 (await) println("World") } println("Hello") } /* 出力 Hello World */
runBlocking
: CoroutineScopeの生成関数launch
: Coroutineの生成関数。CoroutineScopeに属するメソッドdelay()
: サスペンド関数。コルーチンを停止するが、カレントスレッドをブロックしない。await
にあたる
サスペンド関数の生成
サスペンド関数
Suspending functions can be used inside coroutines just like regular functions, but their additional feature is that they can, in turn, > use other suspending functions (like delay in this example) to suspend execution of a coroutine.
コルーチンの実行を中断する。つまりサスペンド関数自体が、await
に相当する。
suspend
修飾子をつける。また内部でサスペンド関数を呼ぶことができる。
suspend fun doWorld() { delay(5000L) // 内部でサスペンド関数を呼ぶことができる println("World") } fun main = runBlocking { // this: CoroutineScope launch { doWorld() } println("Hello") } /* Hello World */
スコープビルダー
CoroutinScopeの生成は以下がある。
coroutineScope{}
は、サスペンド関数なので、runBlocking{}
で一旦スコープを生成する必要がある。
coroutineScope使用例
import kotlinx.coroutines.* fun main() { runBlocking { doWorld() // サスペンド関数 (await) println("runBlocking end") } println("main end") } suspend fun doWorld() = coroutineScope { // this: CoroutineScope launch { delay(1000L) println("World") } println("Hello") } /* 出力結果 Hello World runBlocking end main end */
スコープの生成と並列性
coroutineScope {}
は内部に複数の並列処理を実行するためのサスペンド関数を含めることができる。
import kotlinx.coroutines.* fun main() = runBlocking { doWorld() // サスペンド関数 (await) println("Done") } suspend fun doWorld() = coroutineScope { // this: CoroutineScope launch { // async delay(2000L) // await println("World 2") } launch { // async delay(1000L) // await println("World 1") } println("Hello") } /* Hello World 1 World 2 Done */
明示的なjob
An explicit job
launch
コルーチンビルダーは、Job
オブジェクトを返すので、それを使って、コルーチンの完了を明示的に待機させることができる。
job object
: 起動されたコルーチンに対するハンドル
例として、子供のコルーチンの完了を待機させる
// CoroutineScope内部 val job = launch { // async delay(1000L) // await println("World") } println("Hello") job.join() // 子供のコルーチンが完了するまで待機する println("Done") /* Hello World Done */
コルーチンは軽量である
スレッドと違い、コルーチンを何千個生成してもCPUの負荷に影響を与えない。
import kotlinx.coroutines.* fun main() = runBlocking { repeat(100_000) { // launch a lot of coroutines launch { delay(5000L) print(".") } } }
連想エンティティ - 多対多の関係を表すテーブル
- 命名は非公式
- 多対多の関係を表すための一つのアプローチとして、連想エンティティ(結合テーブル)がある
デリゲート
Delegation
Kotlinは、特定のインターフェースの処理を指定したインスタンスに自動的に委譲させることができる。
例: Baseインターフェースの処理をBaseImplインスタンスに移譲するDerivedクラス
interface Base { print() } class BaseImple : Base { override fun print() { print(x) } } // Base処理はインスタンスbに移譲する class Derived(b: BaseImpl) : Base by b fun main() { val b = BaseImpl(10) Derived(b).print() // 10 }
オーバーライドの動作について
- メソッド: デリゲート元が呼び出される
- プロパティ: 呼び出したインスタンスに依存する
interface Base { val message: String fun print() fun printMessageLine() } class BaseImpl(val x: Int) : Base { override val message = "BaseImpl: x = $x" override fun print() { println(message) } override fun printMessage() { print(x) } } class Derived(b: Base) : Base by b { // bからはアクセスされない override val message = "Message of Derived" // オーバーライドメソッドが呼び出される override fun printMessage() { print("abc") } } val b = BaseImpl(10) Derived(b).print() // x = 10 Derived(b).printMessage() // "abc"
関連
Scope関数
Scope functions
標準ライブラリに含まれているスコープ関数。 スコープ関数の目的は、一つのオブジェクトのコンテキストを内含するコードブロックを実行することである。
let
run
with
apply
also
スコープ関数を使うことで、より正確にリーダブルに書くことができるようになる
let
で書いた場合
Person("Alice", 20, "Amesterdam").let { println(it) it.moveTo("London") it.incrementAge() println(it) }
let
を使わない場合
val alice = Person("Alice", 20, "Amsterdam") println(alice) alice.moveTo("London") alice.incrementAge() println(alice)
関数の選択
Function selection
Function | Object reference | 返り値 | 拡張関数かどうか | ユースケース |
---|---|---|---|---|
let |
it |
ラムダの結果 | YES | 関数のチェーン, アンラップ |
run |
this |
ラムダの結果 | YES | objectの初期化とメソッドの実行 |
run |
- | ラムダの結果 | NO コンテキストオブジェクトを持たない | property = run { 式 } |
with |
this |
Contenxt object | NO 引数にcontext objectを取る | with(object) { 処理 } |
apply |
this |
Context object | YES | objectの構成 |
also |
it |
Context object | YES | objectを使った処理 |
let
関数の選択- 非nullオブジェクトのラムダ式を実行する
- ローカルスコープ内の変数として式を導入する
apply
関数の選択- Objectの構成
run
- Objectの構成と、結果の計算
non-extension run
- 式が必要とされる箇所での文の実行
also
- 追加的なエフェクト
with
- Object上で関数呼び出しのグループ化
ただしオーバーラップするため、便利性と相談して決める
Functions
使い方のおすすめ
let
- context object:
it
- 返り値: ラムダの結果
一つ以上の関数をチェインすることができる。
mutableListOf("one", "two", "three", "four", "five") .map { it.length } .filter { it > 3 } .let { println(it) // 更に必要となる関数を呼びだすことが可能 // このラムダの結果が返り値となる }
式が一つの場合は、関数リテラルを渡すことも可能
mutableListOf("one", "two", "three", "four", "five") .map { it.length } .filter { it > 3 } .let (::println) // 関数オブジェクトを渡す
nullオブジェクトのアンラップ
val length = str?.let { // アンラップされる println("let() called on $it") processNonNullString(it) it.length }
with
- context object:
this
- 返り値: ラムダの結果
ラムダの結果を提供しないコンテキストオブジェクト上で関数を呼ぶために使用することをおすすめ。
withは、"with this object, do the following" と読むことができる (このobjectを使って、以下を実行する)
val numbers = mutableListOf("one", "two", "three") with(numbers) { println("'with' は $this 引数を伴って呼ばれる") println("$size 要素を持つ") }
他の例として、objectのプロパティや関数を使って値を計算するためのヘルパーオブジェクトを生成する。
val numbers = mutalbeListOf("one", "two", "three") val firstAndLast = with(numbers) { "The first element is ${first()}, " + " the last element is ${last()}" }
run
- context object:
this
- 返り値: ラムダの結果
with
と同じ目的を持つが、objectの拡張関数let
と同じように呼び出す
runは、ラムダ内に、オブジェクトの初期化と返り値の計算をするのに便利に使える
val result = service.run { // objectのメンバの初期化やメソッドを呼び出す port = 8080 query(prepareRequest()) }
letで書いた場合
val result = service.let { it.port = 8080 it.query(it.prepareRequest()) }
non-extension run
複数の文を、一つの式にまとめる
class App { val hexNumberRegex = run { // 初期化時に内部が実行される val digits ="0-9" val hexDigits = "A-Fa-f" val sign = "+-" Regex("[$sign]?[$digits$hexDigits]+") } } // 出力: [+-]?[0-9A-Fa-f]+ println(App().hexNumberRegex
runでまとめなかった場合
class App { val hexNumberRegex = { // 初期化時に内部は実行されない val digits ="0-9" val hexDigits = "A-Fa-f" val sign = "+-" Regex("[$sign]?[$digits$hexDigits]+") } } // 関数が出力される: Function0<kotlin.text.Regex> println(App().hexNumberRegex
apply
- context object:
this
- 返り値: object
objectの構成で使用する。
"apply the following assignments to the object"と読める (以下の代入をobjectに適用する)
val adam = Person("Adam").apply { // objectの構成 age = 32 city = "London" }
also
- context object:
it
- 返り値: object
"and also do the following with object." またobjectを使って以下を実行する
numbers .also { // objectを使った処理 println("The last elements before adding new one: $it") } .add("four")
デリゲートプロパティ
Delegated propaties
Delegated propertyにすることで、プロパティに対して、遅延初期化や値の監視などをかんたんに実装することができる。
デリゲートの指定
var delegatedPropety: Type by Delegate()
型の後ろに、by
キーワードを使ってデリゲートを指定する。
Standard Delegate
Delegateは自作することができるが、Kotlinは標準で以下のDelegateを用意している。
- Lazy properties
- Observable properties
- 他のプロパティへのデリゲート
- Storing properties in map
Lazy properties
// lazy()関数は、初期化するための関数リテラルを受け取り、Lazy<T>インスタンスを返す val lazyValue: String by lazy { println("computed") "Hello" }
lazy()
デリゲートをつけることで、Lazy<T>
インスタンスが返され、
初回のget()
が実行された際に、lazy()に渡したラムダが実行される。
ローカル変数をデリゲートプロパティとして宣言することができる。
fun example(computeFoo: () -> Foo) { val memoizeFoo by lazy(computeFoo)
Observable properties
Delegates.observable()
は2つの引数を取る: 初期値と変更のハンドラー。
import kotlin.properties.Delegate class User { var name: String by Delegates.ovservable("<no name>") { prop, old, new -> println("$old -> $new") } }
他のプロパティへのデリゲート
プロパティは、他のプロパティに対してそのgetterとsetterをデリゲートすることができる。
デリゲートプロパティになれるのは、
- top-level property
- 同じクラスのメンバ、拡張プロパティ
- 他のクラスのメンバ、拡張プロパティ
var topLevelInt: Int = 0 class MyClass { var delegatedToTopLevel: Int by ::topLevelInt }
class ClassWithDelegate(val anotherClassInt: Int) class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate(val anotherClassInt: Int) { var ddelegedToMember: Int by this::memberInt val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt }
var MyClass.extDelegated: Int by ::topLevelInt
この機能は、deprecatedに対して有効
clas MyClass { var newName: Int = 0 @Deprecated("Use newName insted", ReplaceWith("newName")) var oldName: Int by this::newName }
Storing properties in a map
class User(val map: Map<String, Any?>) { val name: String by map // mapプロパティに移譲 val age: Int by map }
name
やage
はmapから取得される
val json = mapOf("name" to "John Doe", "age" to 25) val user = User(json) println(user.name) // John Doe println(user.age) // 25
自作Delegate
valプロパティに対しては、getValue()
を実装する必要がある。
varプロパティに対しては、setValue()
も実装する必要がある。
import kotlin.reflect.KProperty class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, thank you for delegating '${property.name}' to me!" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$value has been assigned to '${property.name}' in $thisRef.") } }
以下のように使える
class Example { var p: String by Delegate() }