LoginSignup
30
23

More than 5 years have passed since last update.

Kotlin 1.3のCoroutineの使い方①(はじめてのCoroutine)

Posted at

検証環境

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

  • Intellij IDEA ULTIMATE 2018.2
  • Kotlin 1.3.0
  • Gradle Projectで作成
  • GradleファイルはKotlinで記述(KotlinでDSL)

やりたいこと

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

公式:Coroutineのページ

準備

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

dependencies {
    implementation( "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0")
}

基本的に、下記の公式ように追記すれば問題ありません。
Coroutine-Using in your projects

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

intellijでkotlinプロジェクトを作る方法

Your first coroutine

公式サイトでは、はじめてのコルーチンといった感じでサンプルコードが載っています。
Your first coroutine

Run the following code:

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

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

実行結果:

Hello,
World!

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

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

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

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と名前を表示する処理を追記してみました。
実行してみると以下のような結果になりました。

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(...)に置き換えられるんで、試してみよう!同じ結果になるはずだよ。

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

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

特にコンパイルエラーもないので、実行してみます。
実行結果は以下のようになります。

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(サスペンド)関数はコルーチンかその他のサスペンド関数からのみ呼び出せるよ

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

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

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

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

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関数が特別なようです。
ここでいう threadthread関数 で生成したスレッドのことを指していると思われます。
delay関数の実装部分をみてみると

public suspend fun delay(timeMillis: Long) {
    〜省略〜
}

確認すると関数の宣言で suspend なるキーワードが使われています。

まとめ

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
23