LoginSignup
41
42

More than 1 year has passed since last update.

よく分かってないKotlin Coroutine

Last updated at Posted at 2023-01-15

はじめに

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のおかげで簡単に非同期処理が書けるんだなと改めて感じました。

ではまたっ!

41
42
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
42