気軽に楽しくプログラムと遊ぶ

自分が興味があってためになるかもって思う情報を提供しています。

Kotlin Coroutineの使い方

以下を写経しつつ、理解をしていきます。
Kotlin の Coroutine を概観する - Qiita

Coroutineとは?

  • 軽量なThreadのようなもの
  • 他の処理をブロックせずに並列処理を行える
  • Threadは無視できないコストがかかるが、Coroutineは気にしなくてもよいレベルのコストしかかからない

Coroutineの作成と利用

non-blocking関数とblocking関数があります。
違いは、main関数の処理をブロックするかどうか。
ブロックとは、一時停止してCoroutine処理を実行することを指す。

non-blocking関数 launch

以下のように実装することでCoroutineを生成。
Coroutineは、launch内の処理をmainスレッドをブロックすることなく、並列で処理することができる。

fun main(args: Array<String>) {
    println("start")
    launch {
        println("coroutine!")
    }
    // Thread.sleepがないとlaunchは実行スレッドをブロックしないため
    // coroutine!が表示されず、endが表示されて終了する
    Thread.sleep(1000)
    println("end")
}

実行結果は以下。Thread.sleepがあるので、coroutine!が表示されるのを待ってから、endが表示される。

start
coroutine!
end

他のコルーチン終了を待機する Job#join()

launchは戻り値でJobを返却するため、他のコルーチン終了を待機するには、以下のように実装可能です。

fun main(args: Array<String>) = runBlocking() {
    println("start")
    launch {
        println("coroutine!")
    }.join()
    println("end")
}

blocking関数 runBlocking

Coroutine(ラムダ)を生成し、実行すると実行スレッドをブロックしつつ、内部で並列処理を実行できる

fun main(args: Array<String>) {
    println("start")
    val text = runBlocking {
        "coroutine!"
    }
    println(text)
    println("end")
}

実行結果は以下。runBlockingは生成したラムダ内の処理結果を返却することができる。

start
coroutine!
end

注意点

  • runBlockingはCoroutineは、非同期処理内(Coroutine内)から呼び出してはいけない
  • 同期処理と非同期処理の境目で利用されることを想定しているため。

non-blocking関数 async/await

lauchのようにブロックしないで非同期処理を行いつつ、戻り値の結果を受け取りたい場合、asyncを利用する。

fun main(args: Array<String>) = runBlocking() {
    println("start")
    val text = async {
        "coroutine!"
    }.await()
    println(text)
    println("end")
}

asyncの戻り値のDeferred インタフェースに実装されているawaitを呼び出すと、Coroutine終了まで
現在のコルーチンを中断し、終了したCoroutineの戻り値を取得する。

Suspending Functionとは?

Coroutineを中断させることができる関数を「Suspending Function」と言う。

suspend fun doSomething() {
    delay(1000)
    println("something")
}

suspend modifierを付けることでCoroutineを中断できることを示します。
Coroutine内で呼び出す関数はsuspendをつけないとコンパイルエラーになります。

上記で呼び出しているdelayもSuspending Functionになります。

また、launch、runBlockingはラムダを用いてCoroutineを生成していましたが、これは無名のSuspending Lambdaになっています。

lauchの定義は以下の通りで、blockのところで無名のSuspending Lambdaを生成しています。

public fun launch(
    context: CoroutineContext = DefaultDispatcher,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job

Suspending Functionの利用方法は以下

fun main(args: Array<String>) {
    println("start")
    runBlocking {
        println("coroutine start")
        greet()
        println("coroutine end")
    }
    println("end")
}

suspend fun greet() { // suspendがないとエラーになる
    delay(1000)
    println("Hello, Coroutine!")
}

greetは、runBlockingで生成したCoroutineを中断し、処理が終わったら、Coroutineを再開します。
そして、実行スレッドに結果があれば戻すことも可能です。