https://raw.githubusercontent.com/JetBrains/kotlin-web-site/master/pages/docs/reference/coroutines.md の雑な訳。Apache 2.0.
Coroutines - コルーチン
コルーチンは Kotlin 1.1+ において 試験的な 機能。
いくつかの API は長期間における処理をする(NetworkID, file IO, CPU- or GPU-intensive work 等)その場合呼び出し元をブロックしてしまうのだけど、コルーチンはブロックをさけ、ブロックを 一次停止する手段を提供する。
コルーチンによって、複雑性がライブラリの中に隠蔽されて非同期プログラミングがよりシンプルになる。プログラムのロジックはコルーチンの中で sequentially として表現されて、基盤を提供するライブラリが非同期性を処理してくれる。ライブラリはユーザーコードをコールバックにラップ、イベントの購読、他のスレッドでの実行などを管理する。それでもコードは順次実行のようなシンプルさを保つ。
他の言語で利用されている非同期メカニズムもKotlinコルーチンを利用して実装する事ができる。以下を含む
-
async
/await
from C# and ECMAScript -
channels and
select
from Go. -
generators/
yield
from C# and Python.
See the description below for libraries providing such constructs.
ブロッキング v.s. サスペンディング
基本的に、コルーチンの処理はスレッドブロッキング無しにサスペンド可能。スレッドブロッキングは得てしてコストが高い(特にロードが高い場合は)。何故なら比較的少ない数のスレッドだけを保持できるので、一つのスレッドがブロックすると他の作業を送らせることになってしまうから。
コルーチンのサスペンディングはほとんどコストが無い。コンテキストスイッチや他の OS の関与が不要。さらになにより、これはユーザーライブラリによってコントロールされるので非常に高い拡張の余地がある。ライブラリの作者によってサスペンションのときに最適化/ログ/インターセプトなどを必要に応じて実装できる。
コルーチンのもうひとつのちがいとして、コルーチンは任意の場所で停止されることは無く、 suspension points と呼ばれる特別にマークされた関数のみで停止される。
サスペンディング関数
サスペンドは suspend
とマークされた特別な関数でのみ起こる:
suspend fun doSomething(foo: Foo): Bar { ... }
このような関数は suspending functions と呼ばれる。何故ならこれを呼ぶタイミングでcoroutine がサスペンドするかもしれないから (ライブラリ側で、実行準備が整っていないと判断すればサスペンドしないこともある). サスペンディングファンクションはパラメーターや返値を持つことは普通の関数と同じだが、コルーチンや他のサスペンディングファンクションからしか呼べないという違いがある。インライン展開された関数でも同じ。
実際に、コルーチンを開始するためにはサスペンディング関数が必要で、よくサスペンディングラムダで表現される。 例えば例はこのような物: async()
function (from the kotlinx.coroutines
library):
fun <T> async(block: suspend () -> T)
この async()
は通常関数で (suspending function
ではない), だが、 block
パラメーターを取るラムダはサスペンディングファンクションで、 suspending lambda と呼ばれる。この中からはサスペンディングファンクションを呼ぶことが可能で
async {
doSomething(foo)
...
}
ノート: 現在、サスペンディングファンクションの型は
supertype
としては使えず、非同期サスペンディング関数もサポートされない。
コルーチンのアナロジーとして await()
もまたサスペンディングファンクションであり (そのため、 async {}
blockから呼び出せる)他のコルーチンの実行まで、コルーチンをサスペンドさせる
async {
...
val result = computation.await()
...
}
async/await
関数がどのように動くかの詳細は kotlinx.coroutines
の中で見つけることができる こちら.
await()
や doSomething()
は通常のファンクション、インライン化されていないファンクションリテラルや通常 main の中では呼び出せない。
fun main(args: Array<String>) {
doSomething() // ERROR: Suspending function called from a non-coroutine context
async {
...
computations.forEach { // `forEach` is an inline function, the lambda is inlined
it.await() // OK
}
thread { // `thread` is not an inline function, so the lambda is not inlined
doSomething() // ERROR
}
}
}
更に、サスペンディング関数は仮想関数たりえ、オーバーライドされる場合も suspend
modifier の指定が必須である。
interface Base {
suspend fun foo()
}
class Derived: Base {
override suspend fun foo() { ... }
}
@RestrictsSuspension
アノテーション
Extension functions (and lambdas) も suspend
とマークが可能で通常の suspend 関数と同様に動く. これによって DSLs や他の API もこの拡張を使うことができる。。いくつかのケースでは、ライブラリの作者は、Suspending function の中でユーザーが任意にサスペンドすることを禁止したいかもしれない。
そのため @RestrictsSuspension
アノテーションを使うことができる。レシーバークラスやインターフェース R
がこれでアノテーションされると、全てのサスペンド拡張は R メンバーへ委譲または。エクステンション同士はお互い無制限に委譲することはできないので、これはライブラリ作者が R
のメンバーを呼んでいるタイミングのサスペンドをライブラリ作者が完全にコントロールできることを意味する。(ここらへんなんとなく解るけど訳はゴミ
想定的に希なケースとして、全てのサスペンドはライブラリで把握したいと思うかも知れない。例えば、buildSequence()
関数を(see below) を実装しているときなどに、以下の事を確実にする必要がああります。
コルーチン内のどのようなsuspending call も yeld() または yeldAll() で終了し、他の関数では終了しない。
これが SequenceBuilder
が @RestrictsSuspension
でアノテーションされている理由です:
@RestrictsSuspension
public abstract class SequenceBuilder<in T> { ... }
See the sources on Github.
コルーチンの内部挙動
このばで詳細な開設はしないが、何が重要かだけ。
コルーチンはコンパーラー技術で実装されていて(JVM または OS 技術は利用していない)、サスペンションはコード変換で動く。基本的に、全てのサスペンディング関数(最適化が実行されるかも知れないがここでは触れない)は、サスペンディングコールに対応したステートマシーンに変換される。サスペンションがおこると、次の状態がコンパイラが生成したフィールドに保存される(実行場所と、ローカル変数など)。コルーチンがレジュームされると、ローカル変数がリストアされて、中断された場所から実行が再開される。
サスペンドされたコルーチンは保存、引き渡し可能。そのようなオブジェクトの型は Continuation
。ここで触れたような全体的なコード変換の詳細は次で詳細に書かれている corresponds to the classical Continuation-passing style. 従って、サスペンド関数は continuation
という追加引数もを持っている。
コルーチンがさらにどのように動くかの詳細は、 this design document で見つかる。実装された言語機能は Kotlin のコルーチンほど一般的ではないかもしれないが、他の言語での async
/ await
(C#やECMAScript 2016など)の同様の記述も参考になる。
コルーチンの状況
コルーチンのデザインは 実験的 であり, 将来のリリースで変更される可能性がある. コンパイル時にフラグを設定しないと The feature "coroutines" is experimental が表示される。非表示にするためには明示的なopt-in flag が必要.
エクスペリメンタルなステータスのため、コルーチン関係の API で標準ライブラリに同梱されている物は kotlin.coroutines.experimental
package にある. デザインが Fix されたら kotlin.coroutines
にリフトアップされる予定。experimental package 自体は後方互換性のため(他のアーティファクトとして?)維持されるんじゃないかな.
重要: ライブラリ作者にも同じコンベンションに従うことをオススメ "experimental" を追加して、例えば com.example.experimental
のような suffix をコルーチンを使うパッケージに使って欲しい。最終的に APIが Fix したら
* 全ての API を com.example
にコピーし(experimental 接頭辞を削除する),
* experimental package を後方互換性のために残しておく
これによって、ユーザーのマイグレーション問題を最小化できる
Standard APIs
コルーチンの中核は以下の3つ
Coroutines come in three main ingredients:
- 言語サポート (i.s. suspending functions
, 上で書いた物);
- Kotlin Standard Library の 低レベルコアAPI
- 直接ユーザーコードに現れる high-level APIs
Low-level API: kotlin.coroutines
ローレベルAPIは比較的小さい。高レベルAPIを作ること以外に使われるべきでない。2つのパッケージからなって居る。
- kotlin.coroutines.experimental
主な型とプリミティブがある。例えば:
-- createCoroutine()
,
-- startCoroutine()
,
-- suspendCoroutine()
;
- kotlin.coroutines.experimental.intrinsics
同じく低レベルの命令。例えば suspendCoroutineOrReturn
.
これらの API の更に詳しい用法は here.
Generators API in kotlin.coroutines
kotlin.coroutines.experimental
のうち、"application-level" の関数は以下の二つ
- buildSequence()
- buildIterator()
これらはシーケンスとも関係があるので、 kotlin-stdlib
の一部として公開されている。実際、これらの関数が generator を実装している。
import kotlin.coroutines.experimental.*
fun main(args: Array<String>) {
//sampleStart
val fibonacciSeq = buildSequence {
var a = 0
var b = 1
yield(1)
while (true) {
yield(a + b)
val tmp = a + b
a = b
b = tmp
}
}
//sampleEnd
// Print the first five Fibonacci numbers
println(fibonacciSeq.take(8).toList())
}
この generates は yield()
function を呼ぶことで、遅延評価で、潜在的に無限のフィボナッチ数列を生成している。シーケンスをイテレーションするときに、次の番号が生成される。すなわち、我々はどんな有限のリストもこのシーケンスから取り出す事ができる。そして、コルーチンはこれを実用的に実施するのに十分簡単な手段となっている。
このようなシーケンスの真の遅延評価のデモをするために、ビルドシーケンス内で、いくつかデバッグアウトプットを実施する事ができる。
import kotlin.coroutines.experimental.*
fun main(args: Array<String>) {
//sampleStart
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 ") }
//sampleEnd
}
このコードを実行すると最初の3要素が print される。数字は STEP
と交互に表示される。これは、処理が遅延で行われたことの例である。
collection または sequence の数値をまとめて評価するために yieldAll()
関数が使える:
import kotlin.coroutines.experimental.*
fun main(args: Array<String>) {
//sampleStart
val lazySeq = buildSequence {
yield(0)
yieldAll(1..10)
}
lazySeq.forEach { print("$it ") }
//sampleEnd
}
buildIterator()
も buildSequence()
と同じような処理をする, ただし返すのは遅延評価のイテレーターである。
SequenceBuilder
class (that bears the @RestrictsSuspension
annotation described aboveに対するサスペンディングエクステンションを書くことで、buildSequence()
に custom yielding logic を追加できる :
import kotlin.coroutines.experimental.*
//sampleStart
suspend fun SequenceBuilder<Int>.yieldIfOdd(x: Int) {
if (x % 2 != 0) yield(x)
}
val lazySeq = buildSequence {
for (i in 1..10) yieldIfOdd(i)
}
//sampleEnd
fun main(args: Array<String>) {
lazySeq.forEach { print("$it ") }
}
他の高レベル API: kotlinx.coroutines
コルーチンに関連して core API だけが Kotlin Standard Library で利用可能。
ほとんどのコルーチンを基本にしたアプリケーションレベル API は他のライブラリとしてリリースされている: kotlinx.coroutines
.このライブラリは以下をカバー
* プラットフォーム非依存の非同期プログラミングを可能にする kotlinx-coroutines-core
:
* このモジュールは、Go-like な channels
を提供。channel
は select
および他の便利なプリミティブを用意,
* kotlinx-coroutines-core
の包括的ガイド;
* JDK8 の CompletableFuture
のための API: kotlinx-coroutines-jdk8
;
* JDK7 以上の Non-blocking IO (NIO) のための API: kotlinx-coroutines-nio
;
* Support for Swing (kotlinx-coroutines-swing
) and JavaFx (kotlinx-coroutines-javafx
);
* RxJava サポート: kotlinx-coroutines-rx
.
これらのライブラリは、共通処理を簡易に行うための便利なAPI群を提供し、また、コルーチンを基本にしたライブラリを提供するための end-to-end の例にもなっている。