前回の記事で、コレクション関数をまとめました。
今回はコルーチンについてまとめておきます。
コルーチンとは
コルーチンはある処理を中断・再開できるインスタンスを指します。
スレッドもこのような動作を行うことができますが、コルーチンのほうが一般的にリソース消費量が少ないとされています。またスレッド間でのデータのやり取りよりもコルーチンとのデータのやり取りのほうが簡単です。このような理由でスレッドではなく、コルーチンが用いられることがあります。
実際に簡単なコルーチンを実行してみます。
import kotlinx.coroutines.* fun main() = runBlocking { launch { delay(1000L) println("World") } println("Hello") } // output: // Hello // World
一般的な上から順に実行されていないことがわかります。
runBlocking
でコルーチンのスコープを生成します。
launch
で実際に新しいコルーチンを生成し、そのまま実行します。
async, await
launch
を紹介しましたが、 async
, await
によるコルーチンの実行も可能です。
launch
では、データの返却ができませんでしたが、 async
, await
ではコルーチンから単一のデータを返すことができます。
import kotlinx.coroutines.* import kotlin.system.measureTimeMillis fun main() = runBlocking { val n = 2 val plus_1 = async { delay(1000L) n + 1 } val mult_3 = async { delay(1000L) n * 3 } val t = measureTimeMillis { val amount = plus_1.await() + mult_3.await() println(amount) } println("Time: ${t}ms") } // output: // 9 // Time: 1008ms
async
は Deferred
オブジェクトを返します。これは特定の型のインスタンスを後で返すオブジェクトです。
Deferred
に対して、 await
を呼び出すことで最終的なデータを受け取ることができます。
Flow
Flow
はこれまでと異なり、複数の値を返すことができます。返す際は、 emit
関数を利用します。
import kotlinx.coroutines.* import kotlinx.coroutines.flow.* fun simple(): Flow<Int> = flow { for (i in 1..3) { delay(100) emit(i) } } fun main() = runBlocking<Unit> { launch { for (k in 1..3) { println("I'm not blocked $k") delay(100) } } simple().collect { value -> println(value) } } // output: // I'm not blocked 1 // 1 // I'm not blocked 2 // 2 // I'm not blocked 3 // 3
Flow
と emit
は、 Sequense
と yield
を知っている人であれば、その関係によく似ていることがわかると思います。
Channel
Channel
は Flow
と同じく複数の値をコルーチンとの間で受け渡すことができます。ただし、 Flow
は collect
といった終端になるメソッド呼び出しが行われるまで実行されませんが、 Channel
は単にコルーチンから呼び出されるだけなので、コルーチン自体は自動的に動き続けます。
そのため、 Channel
では受け取り側の準備に関わらず、データを送信することになります。受け取るまでの間、 Channel
のインスタンスでデータを受け持つ形になります。そのため、指定したバッファを超えてデータが溜まるとエラーとなります。
import kotlinx.coroutines.* import kotlinx.coroutines.channels.* fun main() = runBlocking { val channel = Channel<Int>() launch { for (x in 1..5) channel.send(x * x) channel.close() } for (y in channel) println(y) println("Done!") } // output: // 1 // 4 // 9 // 16 // 25 // Done!
ちなみに、コルーチン側から送るだけでなく、コルーチンに向けて送ることも可能です。
サスペンド関数
コルーチンでは、コルーチンの処理を中断できる関数を呼ぶことができ、これをサスペンド関数と呼びます。これまでのサンプルコードでも delay
というサスペンド関数を利用しています。
サスペンド関数は、通常の関数と違い、コルーチンのスコープ内か、サスペンド関数からしか呼び出すことができません。
サスペンド関数を定義して利用してみます。
import kotlinx.coroutines.* import kotlin.system.measureTimeMillis suspend fun delay_2_sec() { delay(2000L) } fun main() = runBlocking<Unit> { launch { println("call delay.") val time = measureTimeMillis { delay_2_sec() } println("finish delay. $time msec") } println("block ended.") } // output: // block ended. // call delay. // finish delay. 2004 msec
サスペンド関数は suspend
キーワードを付けて関数を定義します。これにより複雑なコルーチンも、関数に処理をまとめる事ができます。
まとめ
今回は簡単なコルーチン周りの使用方法をまとめました。
キャンセル等の説明を省きましたが、コルーチンの真骨頂はキャンセルもできることにあるかと思います。 launch
、 async/await
、 Flow
、 Channel
を使い分けていくことで、ノンブロッキングな処理を簡単に達成できるので活用していきたいですね。