Coroutineのキャンセルとタイムアウト

kotlinlang.org

コルーチンの実行をキャンセル

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 ...
*/

計算コードをキャンセルできるようにする

  1. yield関数を使って、キャンセルされたかどうかを確認するサスペンド関数を用意する
  2. ループする際に、キャンセルされていないかどうか確認するようにする
// 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")
}

コルーチンの基本

kotlinlang.org

コルーチンとは

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(".")
        }
    }
}

デリゲート

kotlinlang.org

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"

関連

yossan.hatenablog.com

ログ Log

ログレベル 内容 メソッド
Assert 重大エラーメッセージ Log.wtf()
Error エラーメッセージ Log.e()
Warn 警告メッセージ Log.w()
Info 通常メッセージ Log.i()
Debug 詳細なメッセージ, 製品版アプリでも出力される Log.d()
Verbose 詳細なメッセージ, 製品版アプリでは出力されない Log.v()
Log.i("tag", "message")

Scope関数

kotlinlang.org

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")

デリゲートプロパティ

kotlinlang.org

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
}

nameageは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()
}