目次
、 Coroutineのドキュメントが更新されていました。 改めて読んでみます。 尚、ちょっと長めの内容になってしまったので 2記事に分けています。
簡単な Coroutine の例
Coroutine は、 軽いスレッドです。 CoroutineScope
のコンテキストの中で、 launch
コルーチンビルダ を用いて実行することができます。 GlobalScope
を用いて実行した場合は、 コルーチンのライフタイムはアプリケーション終了までとなります。
1 2 3 4 5 6 7 8 |
fun main(args: Array<String>) { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") Thread.sleep(2000L) } |
delay
はスレッドをブロックしない特別なサスペンディングファンクションです。
上のコードで GlobalScope.launch { ... }
を thread { ... }
に変更し、 delay(...)
を Thread.sleep(...)
に変更すると Coroutine を使わずに同じ結果を出力することができます。
runBlocking によるスレッドブロック
メインスレッドをブロックするのには runBlocking
コルーチンビルダ が使えます。 上のコードの Thread.sleep
を、 runBlocking
を用いて次のように書き換えても同じ結果になります。
1 2 3 4 5 6 7 8 9 10 |
fun main(args: Array<String>) { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") runBlocking { delay(2000L) } } |
いっそのこと、次のように main
関数 を runBlocking
コルーチンビルダ によって生成される コルーチン にしてしまうこともできます。
1 2 3 4 5 6 7 8 |
fun main(args: Array<String>) = runBlocking { GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") delay(2000L) } |
別のコルーチンが動作している間、ある時間だけ遅延させるというのは良い方法ではありませんから、 バックグラウンドジョブが完了するまで明示的に、非ブロッキングの方法で、待ちましょう。
runBlocking
を使うと、 コルーチンのテストを記述することもできます。
1 2 3 4 5 6 |
class MyTest { @Test fun testMySuspendingFunction() = runBlocking<Unit> { // here we can use suspending functions using any assertion style that we like } } |
Structured Concurrency – 構造化された並行性
GlobalScope.launch
を使うと、トップレベルのコルーチンが作成されます。 軽量とはいえ、実行中にはメモリリソースを消費します。 もし新しく起動したコルーチンへの参照を忘れてしまうと、コルーチンは終了しません。 コルーチンのコードがハングした場合(たとえば、長すぎると誤って遅延する)、コルーチンが多すぎてメモリが足りなくなった場合はどうなりますか。 起動されたすべてのコルーチンへの参照を保持させるのはエラーがが起きやすい方法です。
より良い方法は、structured concurrency を使うことです。 GlobalScope
でコルーチンを実行するのではなく、 スレッドで通常行うように、特定のスコープでコルーチンを実行します。 スレッドは常にグローバルです。
上のコルーチンの例を書き換えてみます。 runBlocking
を含むすべてのコルーチンビルダは、そのコードブロックのレシーバが CoroutineScope
になっています。 外側のコルーチン(サンプルコードではrunBlocking
) は、そのスコープで起動されたすべてのコルーチンが完了するまで完了しないため、明示的に結合することなく、このスコープでコルーチンを起動できます。
1 2 3 4 5 6 7 8 |
fun main(args: Array<String>) = runBlocking { // this: CoroutineScope launch { // launch new coroutine in the scope of runBlocking delay(1000L) println("World!") } println("Hello,") } |
スコープビルダ
コルーチンビルダによって生成されるコルーチンのスコープもありますが、 coroutineScope
ビルダ を使って独自のスコープを作ることも可能です。 coroutineScope
ビルダ はコルーチンスコープを作成し、 スコープ内で起動したすべてのコルーチンが完了するまで待機します。 runBlocking
と coroutineScope
の主な違いは、 実行中スレッドをブロックするか否かです。 すべての子が完了するのを待つ間、 coroutineScope
は実行中のスレッドをブロックしません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
fun main(args: Array<String>) = runBlocking { // this: CoroutineScope launch { delay(200L) println("Task from runBlocking") } coroutineScope { // Creates a new coroutine scope launch { delay(500L) println("Task from nested launch") } delay(100L) // This line will be printed before nested launch println("Task from coroutine scope") } // This line is not printed until nested launch completes println("Coroutine scope is over") } |
この例での出力は次のようになります。
1 2 3 4 5 |
Task from coroutine scope Task from runBlocking Disconnected from the target VM, address: '127.0.0.1:53493', transport: 'socket' Task from nested launch Coroutine scope is over |
もし coroutineScope
の部分を削除すると次のようになります。
1 2 3 4 5 6 7 8 9 |
fun main2(vararg args: String) = runBlocking { // this: CoroutineScope launch { delay(200L) println("Task from runBlocking") } // This line is not printed until nested launch completes println("Coroutine scope is over") } |
そして出力の順序も変化します。
1 2 |
Coroutine scope is over Task from runBlocking |
Kotlin 1.3: Coroutine の基本 (2 of 2) に続きます。