目次
Kotlin 1.3 で公式にリリースされる coroutine 、 今まで丁寧にドキュメントを読んだことがなかったので、 現時点での (Experimental としての) ドキュメントを読んでまとめてみました。
概要
非同期処理を実現する機能です。
ブロックとサスペンド
Coroutine はスレッドをブロックすることなく停止させられる計算のことです。 スレッドのブロックはしばしばコストが高く、計算処理の遅延を引き起こします。 一方で Coroutine の停止はほとんどコストがかかりません。 また、サスペンドファンクションの位置のみでその停止(サスペンド)が起きます。
Suspending Function
サスペンドファンクションです。 次のように記述します。
1 |
suspend fun doSomething(foo: Foo): Bar { ... } |
Suspending Function を呼び出すところで、 停止するか否かは Kotlin のプログラムが自動で決定します。 ですので止まらないこともあります。 Suspending Function は Coroutine または 他の Suspending Function 内で呼び出すことができます。
実際のところ、 Coroutine を実行するには 1つ以上の Suspending Function が必要になります。 例えば async
関数 は Suspending Function を引数に取ります。
1 |
fun <T> async(block: suspend () -> T) |
次のように async
の引数 block
に ラムダ を渡すと、 ラムダは Suspending Function として扱われます。
1 2 3 4 |
async { doSomething(foo) ... } |
Suspending Function は インターフェース にすることもできます。
1 2 3 4 5 6 7 |
interface Base { suspend fun foo() } class Derived: Base { override suspend fun foo() { ... } } |
@RestrictsSuspension アノテーション
拡張関数とラムダ関数には suspend
修飾子を付与することができます。 これにより、 DSL 作成 や他のユーザが拡張可能なAPIを作ることが可能となります。 いくつかのケースでは、ライブラリの作者はユーザがサスペンドファンクションを追加できないようにする必要があります。
@RestrictsSuspension
アノテーション で これを実現することができます。 レシーバクラスまたはインターフェース R
に @RestrictsSuspension
がついている場合、 全ての サスペンディングな拡張関数は R
のメンバまたは他の拡張関数に委譲する必要があります。 拡張関数は無限に委譲することができないので(プログラムが終了しません)、ライブラリの作者が完全に制御できる R
のメンバを呼び出すことによってすべての中断が確実に行われます。
まれに、すべてのサスペンディングファンクションがライブラリ内で特別な方法で処理される場合に関係します。 たとえば、以下で説明する buildSequence()
関数 を使用してジェネレータを実装する場合、 coroutine
のサスペンドの呼び出しが yield()
または yieldAll()
の呼び出しを終了し、他の関数を呼び出さないようにする必要があります。 これが、SequenceBuilder
に @RestrictsSuspension
アノテーションが付けられている理由です。
1 2 |
@RestrictsSuspension public abstract class SequenceBuilder<in T> { ... } |
Coroutine の内部動作
Coroutine は完全にコンパイル技術によって実装され (VM や OS のサポートは不要です) 、 サスペンションはコード変換によって機能します。 基本的に全てのサスペンディングファンクションは状態がサスペンド呼び出しに該当するステートマシンに変換されます。 停止の前に、次のステートがコンパイラの生成したクラスのフィールドに関連するローカル変数などと共に格納されます。 Coroutine の再開においては、ローカル変数は復元され、ステートマシンは停止後の状態に変化します。
停止された Coroutine は 停止された状態およびローカル変数を保持するオブジェクトとして保存されます。 そのようなオブジェクトの型は Continuation
です。 そしてここに記載する全体的なコード変換は古典的な Continuation-passing スタイルです。 そのため サスペンディングファンクション は 内部に Coroutine
型 の 余分なパラメータをとります。
基本のAPI
Coroutine は 3つの要素から成り立っています。
- サスペンディングファンクションなどの言語サポート
- Kotlin Standard Library 内の低レベルの中核API
- ユーザコードから利用できる高レベルのAPI
低レベルAPI: kotlin.coroutines
2つのメインAPIから成り立っています。
kotlin.coroutines.experimental
createCoroutine()
startCoroutine()
suspendCoroutine()
kotlin.coroutines.experimental.intrinsics
:suspendCoroutineOrReturn
などの固有の低レベル機能を含む
kotlin.coroutines のジェネレータ
kotlin.coroutines.experimental
のアプリケーションレベルの関数は次の通りです。
buildSequence()
buildIterator()
Sequence に関連しているため これらは kotlin-stdlib
内 にあります。 内部的に Coroutine を使って動作しています。
buildSequence()
は低コストで lazy なシーケンスを生成します。 buildIterator()
は lazy なイテレータを作成します。
buildSequence の例
1 2 3 4 5 6 7 8 9 10 11 |
val lazySeq = buildSequence { print("START ") for (i in 1..5) { yield(i) print("STEP ") } print("END") } // Print the first three elements of the sequence lazySeq.take(3).forEach { print("$it ") } |
この例は1から5までのシーケンスを buildSequence
を使って作成するようになっていますが、 呼び出しのところで take(3)
を実行しているため 3つの要素しか作成されず、 print("END")
は実行されません。 もし lazySeq.take(6)
と書いてあれば print("END")
まで実行されます。
高レベルAPI: kotlinx.coroutines
Coroutines に関連している API は Kotlin Standard Library から利用可能です。 主に、 Coroutine をベースにしたライブラリが使用するプリミティブおよびインターフェースから成り立っています。
Coroutines をベースとしたほとんどのアプリケーションレベルの API は 分離されたライブラリ kotlinx.coroutines
としてリリースされています。 このライブラリは次の範囲をカバーしています。
kotlinx-coroutines-core
によるプラットフォームに依存しない非同期プログラミング- JDK 8 のCompletableFutureに基づくAPI:
kotlinx-coroutines-jdk8
JDK 7以降のAPIに基づくノンブロッキングIO(NIO): - Swing (
kotlinx-coroutines-swing
) と JavaFx (kotlinx-coroutines-javafx
) のサポート - RxJavaのサポート:
kotlinx-coroutines-rx
kotlinx-coroutines-nio
これらのライブラリは、一般的なタスクを簡単に行う便利な API として、 また、コルーチンベースのライブラリを構築する方法に関するエンドツーエンドの例として提供されています。