Kotlin 1.3のCoroutineの使い方①（はじめてのCoroutine）

# 検証環境
この記事の内容は、以下の環境で検証しました。

* Intellij IDEA ULTIMATE 2018.2
* Kotlin 1.3.0
* Gradle Projectで作成
* GradleファイルはKotlinで記述（KotlinでDSL）

#   やりたいこと
Kotlin 1.3のリリースと同時に、これまで実験的だったCoroutineの1.0.0がリリースされました。
早速、公式サイトで内容を確認してみると思いの外難しいです。そのため、少しづつ翻訳して、実験しながら理解していいくことにしました。現時点では、Coroutineが何者かも今はわかりません。
最終的には、コルーチンを完全に理解します。
※翻訳の際、意訳が交じることがあります、ご注意ください。

公式：[Coroutineのページ](http://kotlinlang.org/docs/reference/coroutines/basics.html)

# 準備
Coroutineを利用するには、ライブラリを追加する必要があります。
渡しの場合、gradleもKotlinで記述しているので、下記のような記述になります。

```kotlin
dependencies {
    implementation( "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0")
}
```
基本的に、下記の公式ように追記すれば問題ありません。
[Coroutine-Using in your projects](https://github.com/kotlin/kotlinx.coroutines/blob/master/README.md#using-in-your-projects)

intellijでの設定がわからない方は下記を参照するといいと思います。

[intellijでkotlinプロジェクトを作る方法](http://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html)

# Your first coroutine
公式サイトでは、はじめてのコルーチンといった感じでサンプルコードが載っています。
[Your first coroutine](http://kotlinlang.org/docs/reference/coroutines/basics.html#your-first-coroutine)

> Run the following code:

「まずは、下記のコードを実行せよ」と言われたので、実際に動かしてみました。

```kotlin
import kotlinx.coroutines.*

fun main() { 
    GlobalScope.launch { // launch new coroutine in background and continue
        delay(1000L)
        println("World!")
    }
    println("Hello,") // main thread continues here immediately
    Thread.sleep(2000L)
}
```
実行結果：

```text
Hello,
World!
```

公式サイトと同様に表示されたので一安心です。
しかし、釈然としません。 初めてみるクラスや関数が多いです。
1つずつ調べてみました。

|クラスや関数|説明|
|:--|:--|
|GlobalScope| [GlobalScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html)はトップレベルのコルーチンを起動できます。しかし、通常はアプリごとのスコープ[CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html)、を利用すべきです。|
|[launch](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html)関数|新しいコルーチンを起動します。その際、起動したスレッドを止めることはしません。（同期はとらない）なので、今回のサンプルコードだとmain関数の処理を止めることなく、処理を実行します。戻り値としてJob型の値を返します。さらにJobを利用するとキャンセルも可能です。|
|[delay](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html)関数|main関数のスレッドを止めずに、コルーチンを一時停止させます。引数にミリ秒を指定します。|

どうやらコルーチンはmain関数のスレッドとは別物として実行するみたいです。
スレッドが本当に違うのか、下記のコードを実行し、確認してみました。

```kotlin
package step01

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.LocalTime

fun main(args: Array<String>) {

    println("main関数を実行しているスレッドはid:${Thread.currentThread().id}、name:${Thread.currentThread().name}" +
            "¥n で時間は${LocalTime.now()}")

    GlobalScope.launch {
        println("GlobalScope.launch拡張関数直下のスレッドはid:${Thread.currentThread().id}、name:${Thread.currentThread().name}" +
                "¥n で時間は${LocalTime.now()}")
        
        delay(1000L)
        println("World!")
    }

    println("Hello直前のスレッドはid:${Thread.currentThread().id}、name:${Thread.currentThread().name}" +
            "¥n で時間は${LocalTime.now()}")
    
    println("Hello,")
    Thread.sleep(2000L)
}
```
所々に、スレッドのIDと名前を表示する処理を追記してみました。
実行してみると以下のような結果になりました。

```text
main関数を実行しているスレッドはid:1、name:main¥n で時間は17:03:49.722048
GlobalScope.launch拡張関数直下のスレッドはid:12、name:DefaultDispatcher-worker-1¥n で時間は17:03:49.836755
Hello直前のスレッドはid:1、name:main¥n で時間は17:03:49.862578
Hello,
World!
```
main関数のスレッドはIDが **1**、コルーチンは **12** であるため、別のスレッドであることがわかります。

では、引き続き公式サイトを読み進めていきます。
次はこのように記述しています。

>Essentially, coroutines are light-weight threads. They are launched with launch coroutine builder in a context of some CoroutineScope.
> Here we are launching a new coroutine in the GlobalScope, meaning that the lifetime of the new coroutine is limited only by the lifetime of the whole application.

意訳込みですが、こんなこと行っています。

>コルーチンは軽量なスレッドです。CoroutineScopeのcontextにあるコルーチンビルダーを起動し、そして、コルーチンを最終的に起動します。
>今回は、GlobalScopeで起動しているため、このコルーチンの寿命はアプリケーションと同じです。

なるほど、CoroutineScopeを利用するべきなんですね。

続けて読み進めていきます。

> You can achieve the same result replacing GlobalScope.launch { ... } with thread { ... } and delay(...) with Thread.sleep(...). Try it.

意訳込みですが訳すと・・・

> GlobalScope.launch { ... } はthread { ... }に、delay(...) は Thread.sleep(...)に置き換えられるんで、試してみよう！同じ結果になるはずだよ。

公式サイトに載っている通り、置き換えてみました。
置き換え後のコードは以下のようになります。

```kotlin
fun main(args: Array<String>) {

    println("main関数を実行しているスレッドはid:${Thread.currentThread().id}、name:${Thread.currentThread().name}" +
            "¥n で時間は${LocalTime.now()}")

    thread {
        println("GlobalScope.launch拡張関数直下のスレッドはid:${Thread.currentThread().id}、name:${Thread.currentThread().name}" +
                "¥n で時間は${LocalTime.now()}")

        Thread.sleep(1000L)
        println("World!")
    }

    println("Hello直前のスレッドはid:${Thread.currentThread().id}、name:${Thread.currentThread().name}" +
            "¥n で時間は${LocalTime.now()}")

    println("Hello,")
    Thread.sleep(2000L)
}
```
特にコンパイルエラーもないので、実行してみます。
実行結果は以下のようになります。

```text
main関数を実行しているスレッドはid:1、name:main¥n で時間は17:28:47.395996
GlobalScope.launch拡張関数直下のスレッドはid:12、name:Thread-0¥n で時間は17:28:47.399776
Hello直前のスレッドはid:1、name:main¥n で時間は17:28:47.399904
Hello,
World!
```
確かに、同じ結果になりました。

置き換えも出来たところで、読み進めていきます。

>If you start by replacing GlobalScope.launch by thread, the compiler produces the following error:
>Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function

意訳込みですが、

> もし、GlobalScope.launchだけをthreadに置き換えると、コンパイルエラー発生するよ。
> エラー：Suspend（サスペンド）関数はコルーチンかその他のサスペンド関数からのみ呼び出せるよ

実際にコードを記述してみます。

```kotlin
fun main(args: Array<String>) {

    println("main関数を実行しているスレッドはid:${Thread.currentThread().id}、name:${Thread.currentThread().name}" +
            "¥n で時間は${LocalTime.now()}")

    thread {
        println("GlobalScope.launch拡張関数直下のスレッドはid:${Thread.currentThread().id}、name:${Thread.currentThread().name}" +
                "¥n で時間は${LocalTime.now()}")

        delay(1000L)
        println("World!")
    }

    println("Hello直前のスレッドはid:${Thread.currentThread().id}、name:${Thread.currentThread().name}" +
            "¥n で時間は${LocalTime.now()}")

    println("Hello,")
    Thread.sleep(2000L)
}
```

確かにエラーが発生しました。エラーの種類はコンパイルエラーのようです。

![スクリーンショット 2018-11-09 9.22.20.png](https://qiita-image-store.s3.amazonaws.com/0/99960/855c4126-6b87-55f0-2273-3ff0ddc32f49.png)

やはり、サスペンド関数っていうのが、キモのような気がしてきます。

公式サイトでは、このブロック最後の説明です。

> That is because delay is a special suspending function that does not block a thread, but suspends coroutine and it can be only used from a coroutine.

意訳込みですが

> この原因（delay関数の呼び出しがコンパイルエラーになる原因）は、delay関数が特別なサスペンド関数からです。ここでいう特別とは、threadをブロックしません。しかし、コルーチンかサスペンド関数からしか呼び出せないよ。

やっぱりdelay関数が特別なようです。
ここでいう **thread** は **thread関数** で生成したスレッドのことを指していると思われます。
delay関数の実装部分をみてみると

```kotlin
public suspend fun delay(timeMillis: Long) {
    〜省略〜
}
```
確認すると関数の宣言で **suspend** なるキーワードが使われています。

# まとめ
ここまででわかったことは、以下になると思います。

* コルーチンはスレッドを生成して実行する処理であること
* Suspend（サスペンド）なるものがあること
* サスペンド関数はコルーチンかサスペンド関数からしか呼び出せないこと