Smile Engineering Blog

ジェイエスピーからTipsや技術特集、プロジェクト物語を発信します

【Kotlin】Kotlin Coroutines で非同期処理を行う

今回は、Kotlin Coroutines について解説します。

なお、ここに掲載しているソースコードは以下の環境で動作確認しています。

  • Android Studio Chipmunk | 2021.2.1 Patch 1
  • JDK 11.0.12
  • Android Gradle Plugin 7.2.1
  • Kotlin 1.7.0
  • Gradle 7.4.2
  • org.jetbrains.kotlinx:kotlinx-coroutines-core 1.6.3

そして、アプリの build.gradle ファイルに kotlinx-coroutines-core をインポートしています。

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3"
}

基本

Coroutines とは Kotlin にて非同期処理を実現する機能です。

例えば、Coroutines で非同期処理を実現する場合、以下のように実装します。

fun main() {
    // 新しい CoroutineScope を実行する。
    runBlocking {
        // 新しい CoroutineScope を実行しつつ、
        // 現在のスレッドをブロックしない。
        launch {
            // ノンブロッキングで処理を遅延させる。
            delay(100L)
            println("World!")
        }
        println("Hello,")
    }
}

上記のコードを実行すると、以下の結果を得られます。

Hello,
World!

実装を上から見ると、"World!" を先に標準出力して、後に "Hello," を標準出力するように見えます。

しかし、launch の後に続くブロック内の処理と println("Hello,") は並列に実行されており、さらに launch の後に続くブロックでは、最初に delay(100L) で少し処理を中断しているため、実際には "Hello," から "World!" の順番に標準出力されます。

詳細

ここでは、Coroutines によく登場する以下の 4 つについて解説します。

  • CoroutineScope
  • CoroutineContext
  • Coroutine ビルダー
  • suspend 関数

CoroutineScope

CoroutineScope とは Coroutine の有効範囲です。Coroutines で処理を実行したい場合、この CoroutineScope を開始する必要があります。

また、以下のように CoroutineScope は内部に CoroutineContext を保持しており、CoroutineContext の設定通りに Coroutine を実行します。

public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

CoroutineContext

CoroutineContext とは Coroutine の設定のようなものです。CoroutineContext を保持する CoroutineScope は、CoroutineContext の設定通りに Coroutine を実行します。

以下に、代表的な CoroutineContext の要素を記載します。

要素 説明
Job Coroutine のハンドラ。Coroutine のキャンセルができる。
CoroutineDispatcher Coroutine が動作するスレッドを指定する。
CoroutineName Coroutine の名前。デバッグでこの名前が表示される。
CoroutineExceptionHandler Exception のハンドラ。Coroutine でキャッチできなかった Exception を処理できる。

Coroutine ビルダー

Coroutine ビルダーとは CoroutineScope を開始する関数です。さきほどのサンプルコードでは、runBlockinglaunch が Coroutine ビルダーです。Coroutine ビルダーに Coroutines で実行したい処理を渡すと、その処理は CoroutineScope 内部で実行されます。

以下に、代表的な Coroutine ビルダーを記載します。

Coroutine ビルダー 特徴
runBlocking 現在のスレッドをブロックする。
CoroutineScope の外からでも呼び出せる。
ブロック内部の処理の結果を返せる。
launch 現在のスレッドをブロックしない。
CoroutineScope の内部からのみ呼び出せる。
ブロック内部の処理の結果を返せない。
async 現在のスレッドをブロックしない。
CoroutineScope の内部からのみ呼び出せる。
ブロック内部の処理の結果を返せる。

上記の 3 つの内、launch と async については、CoroutineScope の拡張関数として定義されているため、CoroutineScope の内部からしか呼び出すことができません。runBlocking については、CoroutineScope の拡張関数ではないため、CoroutineScope の外からでも呼び出すことができます。

suspend 関数

suspend 関数とは、スレッドをブロックせずに処理を一時停止し、そして再開できる関数のことです。さきほどのサンプルコードでは、delay が suspend 関数です。delay は処理を一時停止する関数ですが、現在のスレッドをブロックせずにスレッドプールに一時返却するため、delay で一時停止している間、別の処理がそのスレッドを使用できます。

また、suspend 関数は suspend 関数からしか呼ぶことができません。よって、例えば delay を呼び出すためには、その呼び出し元の関数に suspend を付与する必要があります。

// NG
fun doWork() {
    delay(100L)
    println("Do work.")
}
// OK
suspend fun doWork() {
    delay(100L)
    println("Do work.")
}

参考

コルーチンの基礎