Kotlin 1.3: Coroutine の基本 (1 of 2)


Coroutine(コルーチン)のドキュメントが更新されていました。 改めて読んでみます。 尚、ちょっと長めの内容になってしまったので 2記事に分けています。

簡単な Coroutine の例

Coroutine は、 軽いスレッドです。 CoroutineScope のコンテキストの中で、 launch コルーチンビルダ を用いて実行することができます。 GlobalScope を用いて実行した場合は、 コルーチンのライフタイムはアプリケーション終了までとなります。

delay はスレッドをブロックしない特別なサスペンディングファンクションです。

上のコードで GlobalScope.launch { ... }thread { ... } に変更し、 delay(...)Thread.sleep(...) に変更すると Coroutine を使わずに同じ結果を出力することができます。

runBlocking によるスレッドブロック

メインスレッドをブロックするのには runBlocking コルーチンビルダ が使えます。 上のコードの Thread.sleep を、 runBlocking を用いて次のように書き換えても同じ結果になります。

いっそのこと、次のように main 関数 を runBlocking コルーチンビルダ によって生成される コルーチン にしてしまうこともできます。

別のコルーチンが動作している間、ある時間だけ遅延させるというのは良い方法ではありませんから、 バックグラウンドジョブが完了するまで明示的に、非ブロッキングの方法で、待ちましょう。

runBlocking を使うと、 コルーチンのテストを記述することもできます。

Structured Concurrency – 構造化された並行性

GlobalScope.launchを使うと、トップレベルのコルーチンが作成されます。 軽量とはいえ、実行中にはメモリリソースを消費します。 もし新しく起動したコルーチンへの参照を忘れてしまうと、コルーチンは終了しません。 コルーチンのコードがハングした場合(たとえば、長すぎると誤って遅延する)、コルーチンが多すぎてメモリが足りなくなった場合はどうなりますか。 起動されたすべてのコルーチンへの参照を保持させるのはエラーがが起きやすい方法です。

より良い方法は、structured concurrency を使うことです。 GlobalScope でコルーチンを実行するのではなく、 スレッドで通常行うように、特定のスコープでコルーチンを実行します。 スレッドは常にグローバルです。

上のコルーチンの例を書き換えてみます。 runBlocking を含むすべてのコルーチンビルダは、そのコードブロックのレシーバが CoroutineScope になっています。 外側のコルーチン(サンプルコードではrunBlocking) は、そのスコープで起動されたすべてのコルーチンが完了するまで完了しないため、明示的に結合することなく、このスコープでコルーチンを起動できます。

スコープビルダ

コルーチンビルダによって生成されるコルーチンのスコープもありますが、 coroutineScope ビルダ を使って独自のスコープを作ることも可能です。 coroutineScope ビルダ はコルーチンスコープを作成し、 スコープ内で起動したすべてのコルーチンが完了するまで待機します。 runBlockingcoroutineScope の主な違いは、 実行中スレッドをブロックするか否かです。 すべての子が完了するのを待つ間、 coroutineScope は実行中のスレッドをブロックしません。

この例での出力は次のようになります。

もし coroutineScope の部分を削除すると次のようになります。

そして出力の順序も変化します。

Kotlin 1.3: Coroutine の基本 (2 of 2) に続きます。