Kotlin はじめてのコルーチン

#0.　はじめに
18年10月にKotlinのコルーチンがexperimentalからstableになりました。
遅ればせながら、コルーチンを触ってみました。

この記事は、これからコルーチンを学習する人向けの記事です。

＊Kotlin1.3、 kotlinx-coroutines1.0.1の環境です。
＊Kotlinが初めての方は、[こちら](https://play.kotlinlang.org/)で気軽に試せるので触ってみてください。先頭に`import kotlinx.coroutines.*`を忘れずに。

#1.　コルーチンとは
[Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3)から引用します。
>コルーチン（英: co-routine）とはプログラミングの構造の一種。サブルーチンがエントリーからリターンまでを一つの処理単位とするのに対し、コルーチンはいったん処理を中断した後、続きから処理を再開できる。


どういうことなのか。簡単なプログラムを例にして説明をします。

```kotlin
fun main() {
    /* ここからコルーチン */
    println("start foo")
    時間のかかる処理
    println("end foo")
    /* ここまでコルーチン */

    println("bar")
}
```
例えば"start foo"から"end foo"をコルーチンとして実行することで、時間のかかる処理のタイミングでmainスレッドがその処理を中断し、中断中は別の処理をすることができます。
ここでは、中断中は"bar"を表示させることにします。
よって、出力結果をこのようなります。

```
start foo
bar
end foo
```

#2.初めてのコルーチン
それでは実際にコルーチンを作成して、スレッドが中断して再開するところをみてみます。
作成するプログラムは、[1.コルーチンとは](#1コルーチンとは)のプログラムに、コルーチンを適用します。説明通りの結果になるか確認します。

コルーチンを作成するにはコルーチンビルダーというものを使います。
コルーチンビルダーには様々ありますが、ここではもっともシンプルな`launch関数`を使います。
使い方は簡単です。`launch関数`にコルーチンとして実行するラムダを渡します。

```kotlin:
fun main() {
    GlobalScope.launch {
        println("start foo")
        delay(1000)
        println("end foo")
    }
    println("bar")
}
```
これで"start foo"から"end foo"まではコルーチンとして実行されます。

なお、`GlobalScope`と`delay関数`はあとで説明します。
`delay関数`は`Thread.sleepメソッド`のようなものだと現時点では思っておいてください。
「時間のかかる処理」を`delay関数`で代替しています。引数として中断したい時間をミリ秒単位で指定できます。

結果はこのようになります。("start foo"が表示されないこともあります。)

```
bar
start foo
```
想定した出力結果になりませんでした。
まず、"bar"が先に表示されてしまいました。
これは`launch関数`がコルーチンの実行をスケジュール化だけして、処理を先に進めてしまうからです。
また、"end foo"が表示されませんでした。原因は、"end foo"から処理を再開をする前にmain関数からリターンして、プログラム自体が終了してしまうからです。

`launch関数`では、mainスレッドの実行を止めることできないので、何か工夫が必要です。
`launch関数`の代わりに`runBlocking関数`というコルーチンビルダーを使うことにします。
`runBlocking関数`は、コルーチンが完了するまで呼び出し出し元のスレッドを停止させるコルーチンビルダーです。

```kotlin:
fun main() {
    runBlocking {
        println("start foo")
        delay(1000)
        println("end foo")
    }
    println("bar")
}
```

当然ですが、これでも期待した出力結果にはなりません。

```
start foo
end foo
bar
```

なぜなら`runBlocking関数`をコールした時点で、コルーチンの処理が終わるまで呼び出し元のスレッドがブロックされるからです。（出力結果として想定したものではありませんが、`delay関数`のポイントで中断および再開はしています。)

それでは先の`launch関数`と組み合わせたらどうなるでしょうか。

```kotlin:
fun main() {
    runBlocking {
        launch {
            println("start foo")
            delay(1000)
            println("end foo")
        }
        delay(500)
        println("bar")
    }
}
```

先述したように`launch関数`はコルーチンの実行をスケジュール化して処理を先に進めてしまうので、"start foo"が表示される前に"bar"が表示されてしまいます。
これを防ぐために"bar"の直前に`delay(500)`を置きます。
（前回と違い、`launch関数`を呼び出す際に`GlobalScope`がない理由はあとで説明します。)


結果はこのようになりました。

```
start foo
bar
end foo
```

想定した出力結果になりました。

どのスレッドで各々が実行されているか調べてみましょう。
また、少しだけKotlinっぽく書いてみます。

```kotlin:
fun main() = runBlocking {
    launch {
        println("$threadName:start foo")
        delay(1000)
        println("$threadName:end foo")
    }
    delay(500)
    println("$threadName:bar")
}

val threadName: String
    get() = Thread.currentThread().name
```

```
main:start foo
main:bar
main:end foo
```
中断する前の処理、中断中の処理、中断から再開した処理、全てmainスレッドで実行されていることが確認できました。

なお、このプログラムは２回中断が発生しています。

*`launch`コルーチンのスケジュール化　→　`delay(500)`で中断(1回目)　→　`launch`の実行開始 → `delay(1000)`で中断(2回目) → `delay(500)` から再開 → `delay(1000)`から再開*


#3.中断はいつ発生するのか
コルーチンの実行が中断され、そして再開される様子を見ることができましたが、中断とはどういう時に発生するのでしょうか。
ドキュメントに[このような](https://kotlinlang.org/docs/reference/coroutines/basics.html#extract-function-refactoring)記載があります。

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

>サスペンド関数は、コルーチンの中で通常の関数のように使えます。通常の関数との違いは、サスペンド関数はコルーチンの実行を中断するために、他のサスペンド関数を使うことです。(この例のdelayのように)

サスペンド関数という新しい用語が出てきました。サスペンド関数とはこのように関数の先頭に`susupend修飾子`がついた関数のことです。

```kotlin:
suspend fun hoge()
```

このドキュメントによるとサスペンド関数をコールすることで中断が発生するようです。
確かに`delay関数`の定義にもこのように`suspend修飾子`がついています。 

```kotlin:
public suspend fun delay(timeMillis: Long)
```

それでは、`delay関数`のように中断を起こすサスペンド関数を作成してみましょう。
せっかくなので、中断から再開するときに値を返すサスペンド関数を作成してみます。
今回は4096bitで表現可能な素数を返す`getPrimeNumber関数`を作成します。

`getPrimeNumber関数`の利用側はこのようにします。

```kotlin:
fun main() = runBlocking {
    println("$threadName:start runBlocking")
    launch {
        println("$threadName:start launch")
        val prime = getPrimeNumber()
        println("$threadName:prime number = $prime")
        println("$threadName:end launch")
    }
    delay(500)
    println("$threadName:end runBlocking")
}
```
大体の流れは、
*`getPrimeNumber関数`をコールしたらmainスレッドはコルーチンを中断　→　その間に"end Blocking"を表示　→　素数が求め終わったら、素数を表示させるところから再開*
です。

次に、サスペンド関数である`getPrimeNumber関数`はどう作成すればいいのでしょうか。
まずは、素数を求めるコードを書く必要がありますが、`BigInterger.probablePrime`という素数を求めるのに便利なメソッドがあります。
このメソッドの詳しい使い方は割愛しますが、`BigInterger.probablePrime(4096, Random())`が素数（正確には「おそらく素数」）を返してくれます。私の手元のマシンでは呼び出してから返ってくるまでに10秒程度かかりました。

次に実際に中断を起こすコードを書いていきます。

```kotlin:
suspend fun getPrimeNumber() = BigInterger.probablePrime(4096, Random())
```
このように書ければシンプルですが、このようにしても`getPrimeNumber関数`で中断されず、mainスレッドが素数を求めるために停止してしまいます。


スレッドを中断させるには`suspendCoroutine関数`をコールする必要があります。
`suspendCroutine関数`はこのように定義されています。この関数もサスペンド関数です。

```kotlin:
inline suspend fun <T> suspendCoroutine(
    crossinline block: (Continuation<T>) -> Unit
): T
```

ラムダが受け取る`Continuationインターフェース`にはこのような拡張関数が定義されています。

```kotlin:
fun <T> Continuation<T>.resume(value: T)
```

この`resumeメソッド`をコールすることで、コルーチンが再開します。

それでは中断はいつ発生するのでしょうか。
あえて、`resumeメソッド`をコールせず、このようにして実行してみてください。

```kotlin:
suspend fun getPrimeNumber() {
    println("$threadName:hoge")
    suspendCoroutine<Any?> {
        println("$threadName:fuga")
    }
    println("$threadName:piyo")
}
```

結果はこのようになります。

```
main:start runBlocking
main:start launch
main:hoge
main:fuga
main:end runBlocking
```
また、このプログラムは永遠に終了しません。なぜなら、コルーチンが再開しないためです。


この結果をみると、"fuga"の後に"end ranBlocking"が表示されているので、"fuga"を表示後、つまり**`suspendCoroutine関数`に渡したラムダの実行終了後に中断が発生している**ことがわかります。
これが中断が発生するタイミングです。

今度は、中断が発生後、約１秒経過してから`resumeメソッド`をコールして再開してみます。

```kotlin:
suspend fun getPrimeNumber() {
    println("$threadName:hoge")
    suspendCoroutine<Int> { cont ->
        println("$threadName:fuga")
        Thread {
            Thread.sleep(1000)
            cont.resume(1234)
        }.start()
    }
    println("$threadName:piyo")
}
```

中断から再開しました。

```
main:start runBlocking
main:start launch
main:hoge
main:fuga
main:end runBlocking
main:piyo
main:prime number = kotlin.Unit
main:end launch
```

また、`getPrimeNumber関数`は素数を返さないので`kotlin.Unit`と表示されてしまっています。

それでは`getPrimeNumber関数`が素数を返すように変更します。`resumeメソッド`に渡した値が`suspendCoroutine関数`の戻り値になるので、このように書けます。

```kotlin:
suspend fun getPrimeNumber(): BigInteger = suspendCoroutine { cont ->
    Thread {
        cont.resume(BigInteger.probablePrime(4096, Random()))
    }.start()
}
```

これで、先ほどの結果で`kotlin.Unit`となっていた箇所に素数が表示されます。

目的である中断の発生タイミングについて、確認できました。

#4.コルーチンビルダーについて少し詳しく
これまでで、`launch関数`や`runBlocking関数`の２つのコルーチンビルダーを使いました。
この２つ以外にも様々なコルーチンビルダーが提供されています。
例えば、先ほど素数を求めるために作成した`getPrimeNumber関数`ですが、`withContext関数`というコルーチンビルダーを使うとこのように書けます。

```kotlin:
suspend fun getPrimeNumber() = withContext(Dispatchers.Default) {
        BigInteger.probablePrime(4096, Random())
    }
```
このコルーチンビルダーは値を返すことができます。
また、第一引数に値を指定することで、コルーチンを実行するスレッドを切り替えています。

実用的なコルーチンビルダーは他にもありますが、この記事ではそれらを紹介しません。
ここでは、この記事でまだ触れていない重要な２つの内容について説明します。

1. コルーチンビルダーは、中断可能な世界へのエントリーポイントのようなもの
2. コルーチンスコープが必要なコルーチンビルダー

####中断可能な世界へのエントリーポイント

まずは１つ目です。
サスペンド関数として`getPrimeNumber関数`を作成し、コールすることでコルーチンが中断されることを見ましたが、このようなコードはコンパイルエラーになります。

```kotlin:
fun main() {
    val primeNumber = getPrimeNumber()
}
```
理由は、**サスペンド関数はサスペンド関数もしくはサスペンドラムダからしかコールできない**というルールがあるからです。
通常のラムダとサスペンドラムダの違いは、関数と同様に`suspend修飾子`の有無です。
例えば、`launch関数`の定義はこのようになっています。

```kotlin:
fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext, 
    start: CoroutineStart = CoroutineStart.DEFAULT, 
    block: suspend CoroutineScope.() -> Unit
): Job (source)
```

`block`の型をみると`suspend修飾子`がついているのがわかります。これはサスペンドラムダを受け取ることを表しています。
このように**コルーチンビルダーはサスペンドラムダを受けることで中断可能な世界へのエントリーポイントを提供しています。**

####コルチーンスコープ

次に２つ目のコルチーンスコープについて。
`launch関数`の定義を見ていただくと、`launch関数`は`CoroutineScopeインターフェース`の拡張関数として定義されているのがわかります。

```kotlin:
fun CoroutineScope.launch(..)
```

よって、`launch関数`をコールするには`CoroutineScope`のインスタンスが必要です。
最初の方のコードで`GlobalScope.launch`と書いていたのはそのためです。
`GlobalScope`は`CoroutineScopeインターフェース`を実装したインスタンスです。

```
object GlobalScope : CoroutineScope
```

`launch関数`をコールするには`CoroutineScope`のインスタンスが必要ですが、
`runBlocking関数`に渡すラムダ内では`GlobalScope.launch{..}`ではなく、シンプルに`launch{..}`と書けます。
この理由は`runBlocking関数`の定義をみるとわかります。

```kotlin:
fun <T> runBlocking(
    context: CoroutineContext = EmptyCoroutineContext, 
    block: suspend CoroutineScope.() -> T
): T (source)
```

`block`のレシーバの型は`CoroutineScope`となっています。
これが理由で、`runBlocking関数`に渡すラムダ内では、`GlobalScope.launch`と書く必要がなかったのです。

レシーバ付きラムダに馴染みがない方のために、少し補足します。
あえて`this`を使って書くとこのようになります。

```kotlin:
runBlocking {
    this.launch {..}
}
```

この`this`は、runBlocking関数が作成した`CoroutineScope`のインスタンスを参照しています。



####補足
コルーチンスコープが導入されたのはkotlinx-coroutines0.26.0からです。
0.26.0がマークされたのが18年9月です。0.26.0より古いバージョンを前提に書かれた記事では`GlobalSope`がないコードを見ることがあるかもしれません。

```kotlin:
// 0.26.0より前
fun main() {
    launch {..}
}

// 0.26.0以降
fun main() {
    GlobalScope.launch {..}
}
```


#5.終わりに
予定では`CoroutineScope`、`CoroutineContext`、`Job`についても書くつもりでしたが、記事が長くなってしまったので、全く触れられませんでした。
コルーチンを使った実用的なコードも同様です。

コルーチンを勉強をしている身ではありますが、何かの機会があれば、それらについても書いてみたいと思います。

この記事が、これからコルーチンを初める方に少しでも役に立てば幸いです。