目次
Kotlin 1.3: Coroutine の基本 (2 of 1) からの続きです。
コルーチンを別の関数にする
launch { ... }
の中身を別の関数に展開してみます。 “Extract function” リファクタリング を実行すると suspend
修飾子 のついた新しい関数を作ることができます。 サスペンディングファンクションは通常の関数と同じようにコルーチンの中で使うことができます。 そして、他のサスペンディングファンクションを呼び出すことができ、またコルーチンの実行を停止させることができます。
1 2 3 4 5 6 7 8 9 10 |
fun main(args: Array<String>) = runBlocking { launch { doWorld() } println("Hello,") } // this is your first suspending function suspend fun doWorld() { delay(1000L) println("World!") } |
外部化された関数が実行中スコープでコルーチンビルダを使っていたらどうなるでしょうか。 その場合は suspend
修飾子 のみでは不十分です。 CoroutineScope
に doWorld
拡張関数 をつけるのがひとつの方法ですが、 万能ではありません。 慣用的な解決策は、明示的なCoroutineScope
を目的の関数を含むクラスのフィールドとして実装するか、 外部クラスが CoroutineScope
を実装するときに暗黙的に指定することです。 最後の手段として、 CoroutineScope
(coroutineContext
) を使用できますが、 このメソッドが実行されるスコープを制御できなくなったため、このようなアプローチは構造的に安全ではありません。 このビルダを使用できるのはプライベートAPIだけです。
コルーチンは軽量である
次のコードを実行すると、およそ1秒後に100,000個のピリオドが出力されます。
1 2 3 4 5 6 7 8 |
fun main(args: Array<String>) = runBlocking { repeat(100_000) { // launch a lot of coroutines launch { delay(1000L) print(".") } } } |
もう少し簡単にして比較してみます。 100,000個のピリオドを出力する3パターンのコードを実行してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
fun printAsItIs() = runBlocking { repeat(100_000) { print(".") } } fun printWithCoroutine() = runBlocking { repeat(100_000) { // launch a lot of coroutines launch { print(".") } } } fun printWithThread() = runBlocking { repeat(100_000) { thread { print(".") } } } |
さて、どれが一番早いでしょうか。 上から順に高速に出力します。 コルーチンの方がスレッドより速いことがわかります。
runBlocking
を使わないパターンも試してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
fun simpleLoopPrint() { for (i in 1..100000) { print(".") } } fun threadLoopPrint() { for (i in 1..100000) { thread { print(".") } } } |
ループで出力しているものが一番早いです。 2番目のループでスレッドを作成するものは、途中でエラーメッセージが出ます。
1 2 3 4 5 6 7 8 |
[6.077s][warning][os,thread] Failed to start thread - pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 4k, detached. Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached at java.base/java.lang.Thread.start0(Native Method) at java.base/java.lang.Thread.start(Thread.java:813) at kotlin.concurrent.ThreadsKt.thread(Thread.kt:42) at kotlin.concurrent.ThreadsKt.thread$default(Thread.kt:25) at com.improve_future.kotlincoroutine.MainKt.threadLoop(Main.kt:28) at com.improve_future.kotlincoroutine.MainKt.main(Main.kt:14) |
デーモンスレッドのようなグローバルコルーチン
Java のスレッドにはユーザースレッドとデーモンスレッドの2種類があります。 デーモンスレッドはプログラムが終了する際に停止を待たず、 全てのユーザースレッドが終了すると自動的に終了します。
1 2 3 4 5 6 7 8 9 |
fun main(args: Array<String>) = runBlocking { GlobalScope.launch { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } delay(1300L) // just quit after delay } |
このコードは、 十分な時間があれば、 1000回 文字列を出力します。 しかし、 delay(1300L)
で 1500ミリ秒 経過後には終了してしまうので、 出力は3回しか行われません。
1 2 3 |
I'm sleeping 0 ... I'm sleeping 1 ... I'm sleeping 2 ... |
1000行出力してから終了したいのであれば、 runBlocking
で生成されたコルーチンスコープの launch
メソッド を使用するのがひとつの方法です。
1 2 3 4 5 6 7 8 9 |
fun main(args: Array<String>) = runBlocking { launch { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } } delay(1300L) // just quit after delay } |
これと同じ結果になりますが、 GlobalScope.launch
で返される Job
の join
メソッド を 実行する方法もあります。 join
を実行することで、 外側のスコープを、該当のコルーチンが終了するまで停止させられます。
1 2 3 4 5 6 7 8 |
fun main(args: Array<String>) = runBlocking { GlobalScope.launch { repeat(1000) { i -> println("I'm sleeping $i ...") delay(500L) } }.join() } |