Kotlinコルーチン入門!
こんにちは。今回はKotlinの「コルーチン(coroutine)」について学んだことを整理しながらまとめました。理解が深まったポイントを中心に紹介します。
また、今回は以下の公式ドキュメントをもとに学習し、この記事で出てくるコードはそこから引用しています。
また、Kotlin Playgroundで簡単に実行できるので、手を動かしながら試してみることもできます。
キーワード
- コルーチン
- runBlocking{}
- launch{}
- async{}
- await()
- 構造化された並行性(structured concurrency)
✅ コルーチンとは?
並行処理(非同期)を簡単かつ安全に書くための仕組み。
複雑なスレッド処理を意識せずに、非同期処理が扱える。
- 非同期:今の処理が終わらなくても次の処理が始まる
- 同期:1つずつ順番に処理が終わるまで待つ
アプリではほとんどが非同期処理(コルーチン)になっています。
例えば、仮に同期処理だと、複雑な処理が終わるまでユーザーには次の画面が見えないなどの問題が発生します。
並列で処理を回すことで、裏で処理をしていると同時にユーザーには次の画面を反映させることが可能になります。
✅ 同期処理
//同期処理
fun main() {
println("Weather forecast")
println("Sunny")
}
このコードでは上から順番にprintlnが動いていく典型的な同期処理コードです。
//非同期処理(コルーチン)A
import kotlinx.coroutines.*
fun main() {
runBlocking {
println("Weather forecast")
delay(1000)
println("Sunny")
}
}
このコードでは上記と違い、runBlocking{}
とdelay()
が登場しました。
- runBlocking{}:コルーチンのエントリポイント。ここからコルーチンだよ、という入り口です。
- delay():指定した時間だけ処理を一時停止する suspend 関数。コルーチン内でのみ使用できます。
//非同期処理(コルーチン)A'
import kotlinx.coroutines.*
fun main() {
runBlocking {
println("Weather forecast")
printForecast()
}
}
suspend fun printForecast() {
delay(1000)
println("Sunny")
}
この様にmain()の外で新しくsuspend関数として定義し、呼び出すこともできます。suspendを付けていないとエラーが出ます。
⇒suspend 関数はコルーチンまたは別の suspend 関数からしか呼び出せないため
非同期とはいえ、今のコードでは結局1つの処理が終わるまで待っているので、同期処理と大差ない結果になります。
✅ 非同期処理
//非同期処理B
import kotlinx.coroutines.*
fun main() {
runBlocking {
println("Weather forecast")
launch {
printForecast()
}
launch {
printTemperature()
}
println("Have a good day!")
}
}
suspend fun printForecast() {
delay(1000)
println("Sunny")
}
suspend fun printTemperature() {
delay(1000)
println("30\u00b0C")
}
ここで新たに登場するのがlaunch{}
です。
- launch{}:非同期処理を開始するための関数。結果を返さず、主に「実行」だけしたい処理に使います。
そうすると、このような順番で出力されます。
//Bの出力
Weather forecast
Have a good day!
Sunny
30°C
この様な順番で出力されると思います。ここでポイントなのが
Have a good day!
が2番目に出力されている点です。
printForecast()
とprintTemperature()
はどちらも1秒待つ操作が組み込まれているため、println("Have a good day!")
が即出力されたということです。
//非同期処理C
import kotlinx.coroutines.*
fun main() {
runBlocking {
println("Weather forecast")
val forecast: Deferred<String> = async {
getForecast()
}
val temperature: Deferred<String> = async {
getTemperature()
}
println("${forecast.await()} ${temperature.await()}")
println("Have a good day!")
}
}
suspend fun getForecast(): String {
delay(1000)
return "Sunny"
}
suspend fun getTemperature(): String {
delay(1000)
return "30\u00b0C"
}
次に登場するのがasync{}
とawait()
です。
- async{}:非同期で処理を実行し、結果(値)を返すコルーチンを開始します。
-
await():async が返す結果(Deferred)を待ち、値を取得します。
⇒ つまり「結果を返す非同期処理」がしたいときに使うのが async。
なので、今回のコードCを実行すると
//Cの出力
Weather forecast
Sunny 30°C
Have a good day!
Have a good day!
が最後に出力されました。
⇒forecast
とtemperature
の2つは並列処理がされていますが、これらが終了するまで、println("Have a good day!")
は待っている状況です。
しかし今のままではコードの意図が分かりにくく感じるかもしれません。
ここで 構造化された並行性(structured concurrency) という概念が登場します。
構造化された並行性とは、複数のコルーチン(非同期処理)を1つのスコープ内で起動し、まとめて管理・終了させる設計手法です。
これにより、コルーチンの開始や終了、例外処理の流れを制御しやすくなり、 安全で読みやすい非同期コードを書くことができます。
//非同期処理C'
import kotlinx.coroutines.*
fun main() {
runBlocking {
println("Weather forecast")
println(getWeatherReport())
println("Have a good day!")
}
}
suspend fun getWeatherReport() = coroutineScope {
val forecast = async { getForecast() }
val temperature = async { getTemperature() }
"${forecast.await()} ${temperature.await()}"
}
suspend fun getForecast(): String {
delay(1000)
return "Sunny"
}
suspend fun getTemperature(): String {
delay(1000)
return "30\u00b0C"
}
上記のコードCをリファクタリングしました。具体的にはmain()内でごちゃごちゃ定義していたforecast
とtemperature
をmain()関数の外で新しくsuspend関数としてgetWeatherReport()
と定義しています。
結果的にmain()関数内を見るとどうでしょうか。
あたかも同期処理のコードAを彷彿とさせる見やすいコードになったのではないでしょうか。
まとめ
今回は非同期処理(コルーチン)について基礎的なものをまとめました。次は「例外とキャンセル」、「コルーチンのコンセプト」についてまとめたいと思います。