はじめに
Android開発でKotlin Coroutineを使用しているのですが、なんとなく使用していて「あ〜非同期処理を簡単に書けて便利だな〜」くらいの理解しかなかったため、今回はCoroutineの理解と備忘録として記事を作成したいと思います。
Coroutineとは
Coroutineとは軽量のスレッドのようなもので、スレッドのように他の処理をブロックすることなく並列に行うことができます。
スレッドよりは軽量で10000個のCoroutineを同時に走らせることもできるみたい。
なにより一番の魅力は非同期処理を同期的に書くことができます。
また、非同期処理実行途中であっても 中断/再開 することが可能です。
Coroutine登場とそれ以前
以下の典型的な非同期処理をCoroutineを使用しない/使用するパターンで比べてみる
1.サーバーから情報を取得する(ここではただの文字列)
2. 取得した情報をViewに反映する
Coroutineを使用しない非同期処理
Coroutineが登場する前のAndroidアプリでの非同期処理は主にコールバックを使用していました。
public static void main(String[] args) {
object : AsyncTask<Void, Int, String?>() {
override fun doInBackground(vararg params: Void?): String? {
String result = Repository.getString()
return result
}
override fun onPostExecute(result: String?) {
binding.name.setText(result)
}
}.execute(null)
}
Coroutineを使用した非同期処理
fun main(args: Array<String>) {
launch(Dispachers.IO) {
// Backgroundスレッド
val result = Repository.getString()
withContext(Dispatchers.Main) {
// Mainスレッド
binding.name.text = result
}
}
}
このようにCoroutineを使用することで簡単にスレッドを切り替えて処理をすることができます。
Coroutineの使い方
Coroutineの作成には CoroutineBuilderを使用します
作成したCoroutineBuilder の種類によって挙動が異なります。
runBlocking
runBlocking は現在のスレッドをブロックします。
UIスレッドで扱う場合は注意が必要です。
fun sample() {
println(1)
runBlocking {
println(2)
}
println(3)
}
// 結果
// 1
// 2
// 3
launch
fun sample() {
println(1)
launch {
println(2)
}
println(3)
}
// 結果
// 1
// 3
launch スコープで囲って上げることでCoroutineを開始できます。
現在のスレッドをブロックしないので launch内の処理が終了する前にプログラムが終了しています。
また、join() を使用することで開始したCoroutine内の処理が終了するまで待機することができます。
fun sample() {
runBlocking {
println(1)
launch {
println(2)
}.join()
println(3)
}
}
// 結果
// 1
// 2
// 3
async
launch 同様現在のスレッドをブロックしないので async 内の処理が終了する前にプログラムが終了します。
fun sample() {
println(1)
async {
println(2)
}
println(3)
}
// 結果
// 1
// 3
await() を使用することで開始したCoroutine内の処理が終了するまで待機することができます。
fun sample() {
runBlocking {
println(1)
async {
println(2)
}.await()
println(3)
}
}
// 結果
// 1
// 2
// 3
Coroutineを扱うために必要な概念
CoroutineContext
Coroutineを実行するための情報がすべて入っている。
job
CoroutineBuilder によって作成されたCoroutine。
CoroutineScope
Coroutineを使用できる範囲を設定する。CoroutineContextを持っている
Dispacher
Coroutineが実行されるスレッドを決定するもの。
4つのDispacherが用意されています。
-
Dispatchers.Main
Mainスレッドを指定。 -
Dispatchers.Default
バックグラウンドスレッドを使用。
CPUに負荷のかかる処理をする際にしようする。(例:ローカルでのListの並び替え等) -
Dispatchers.IO
バックグラウンドスレッドを使用。
IO(Input/Output)操作を処理する際に使用する。(例:DB通信/Http通信) -
Dispatchers.Unconfined
特定のスレッドに限定されない。公式によると普通は使用しないらしい。
Suspend関数
CoroutineにはCoroutine内で処理を中断できるSuspendFunctionなるものが用意されています。
suspend修飾子を付けることで宣言可能です。
suspend関数はCoroutineScope内からでないと呼び出すことができません。
また、suspend関数の処理が終了するまで呼び出し元のCoroutineScope内の処理が止まります。 ←これめっちゃ便利
suspend fun sample(){
// 呼び出し元のCoroutineスコープ内で処理を止めたい処理
}
fun sample() {
runBlocking {
println(1)
delay(1000)
println(2)
}
}
// 結果
// 1
// 1秒中断
// 2
delay() はsuspend関数です
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
Coroutine内での実行スレッド管理
冒頭のCoroutine登場とそれ以前 でも少し触れましたが、Coroutineでは実行するスレッドを簡単に変更/指定することができます。
スレッドを指定してCoroutineを開始する
スレッドを指定してCoroutineを起動する場合
launch(Dispatchers.Main) {
// このCoroutineScope内はMainスレッドで実行される
}
Coroutine内で実行スレッドを変更する
Coroutine内ではwithContext(Dispatchers.(実行モード)) で実行スレッドを簡単に変更することができます。
launch {
// このCoroutineScope内はMainスレッドで実行される
withContext(Dispatchers.IO) {
// このCoroutineScope内はBackgroundスレッド(IO)で実行される
}
}
さいごに
今回はKotlinCoroutineをおさらいしてみました。
僕はエンジニアになった時には既にKotlinCoroutineがあったので登場前の大変さは実体験としては分かっていませんが、Coroutineのおかげで簡単に非同期処理が書けるんだなと改めて感じました。
ではまたっ!