KotlinのrunBlockingについて

採用はこちら

runBlocking は、通常の同期コードからコルーチンを開始するための関数です。

そして、開始したコルーチンの処理が完了するまで 呼び出し元のスレッドをブロック(停止)して待機します。

つまり runBlocking は、次のような役割を持つ関数です。

  • 同期コード → コルーチン世界へ入る
  • コルーチンの完了を待つ
  • 完了するまでスレッドをブロックする

このため runBlocking は 同期処理とコルーチンの橋渡し(ブリッジ)として利用されます。

目次

基本構文

import kotlinx.coroutines.*

fun main() = runBlocking {
    println("Start")

    launch {
        delay(1000)
        println("Coroutine finished")
    }

    println("End")
}

このコードの実行順序は次の通りです。

  1. runBlocking が開始される
  2. launch によって子コルーチンが起動する
  3. delay により1秒待機
  4. 子コルーチンが終了する
  5. runBlocking が終了する

重要なのは、子コルーチンがすべて終了するまで runBlocking は戻らないという点です。

runBlockingの特徴

呼び出し元スレッドをブロックする

通常のコルーチンは、スレッドをブロックしません。

例えば launch や async は非同期で実行されます。

しかし runBlocking は例外で、呼び出し元スレッドを停止させて処理完了を待ちます。

比較すると次のようになります。

関数スレッドの扱い
launchブロックしない
asyncブロックしない
runBlockingブロックする

suspend関数を呼び出せる

suspend 関数は、通常の関数から直接呼び出すことはできません。

例えば次のコードはコンパイルエラーになります。

suspend fun fetchData(): String {
    delay(1000)
    return "data"
}

fun main() {
    fetchData() // エラー
}

しかし runBlocking を使うと呼び出せます。

fun main() = runBlocking {
    val result = fetchData()
    println(result)
}

これは runBlocking が CoroutineScope を作り、その中でコルーチンを実行するためです。

runBlockingの動作イメージ

runBlocking は内部的に次のような処理を行います。

  1. 新しいコルーチンスコープを作る
  2. コルーチンを起動する
  3. 呼び出し元スレッドをブロックする
  4. コルーチンが終了するまで待つ
  5. 完了後にスレッドを再開する

つまり構造としては次のようになります。

main thread
   │
   └ runBlocking
        │
        ├ launch
        ├ async
        └ suspend functions

runBlocking は、このスコープ内のすべての子コルーチンの完了を待ちます。

スレッドの挙動

runBlocking は 呼び出し元スレッドをブロックします。

例えば次のコードを見てみます。

fun main() = runBlocking {

    println(Thread.currentThread().name)

    launch {
        println(Thread.currentThread().name)
    }

}

多くの場合、出力は次のようになります。

main
main

これは、子コルーチンが 明示的なDispatcherを指定していない場合、同じスレッド上で実行されることが多いためです。

ただし、別のDispatcherを指定すれば別スレッドで実行されます。

fun main() = runBlocking {

    launch(Dispatchers.Default) {
        println(Thread.currentThread().name)
    }

}

この場合は、DefaultDispatcher のスレッドで実行されます。

Dispatcherを使った処理

重い処理やI/O処理を別スレッドで実行したい場合は、withContext を使うことが一般的です。

fun main() = runBlocking {

    withContext(Dispatchers.IO) {
        println(Thread.currentThread().name)
    }

}

このコードでは

  • runBlocking は呼び出し元スレッドをブロックする
  • withContext(Dispatchers.IO) はIOスレッドで処理を行う

という動作になります。

参考サイト

Kotlin はじめてのコルーチン – エニグモ開発者ブログ

runBlockingの主な用途

runBlocking は、主に次のような場面で使用されます。

main関数

コマンドラインアプリケーションでは、main からコルーチンを開始するために使われることがあります。

fun main() = runBlocking {
    val data = fetchData()
    println(data)
}

テストコード

コルーチンを使ったコードをテストする際にも利用できます。

@Test
fun testApi() = runBlocking {
    val result = fetchData()
    assertEquals("ok", result)
}

ただし現在のコルーチンテストでは、kotlinx-coroutines-test の runTest が使われることも多くなっています。

同期コードとの橋渡し

Javaの同期APIや既存コードからコルーチンを呼び出す場合にも使われます。

同期コード
   ↓
runBlocking
   ↓
コルーチン処理

Androidでの使用

Androidアプリでは、runBlocking を通常のアプリ処理で使うべきではありません。

理由は、スレッドをブロックしてしまうためです。

例えばUIスレッドで次のコードを実行すると、

runBlocking {
    delay(5000)
}

画面が5秒間停止してしまいます。

これは ANR(Application Not Responding) の原因になります。

Androidでは代わりに次の仕組みを使います。

  • lifecycleScope.launch
  • viewModelScope.launch

これらは UIスレッドをブロックせずにコルーチンを実行できます。

注意点

suspend関数の中でrunBlockingを使わない

suspend 関数の中で runBlocking を使うのは通常避けるべきです。

suspend fun example() {
    runBlocking {
        delay(1000)
    }
}

これはスレッドをブロックしてしまうため、

  • コルーチンの利点を失う
  • スレッド詰まり
  • デッドロックの可能性

などの問題を引き起こす可能性があります。

まとめ

runBlocking は、コルーチンを同期コードから実行するためのブリッジ関数です。

主な特徴は次の通りです。

  • コルーチンを開始する
  • 完了するまで呼び出し元スレッドをブロックする
  • suspend 関数を呼び出せる
  • コルーチンスコープを作る
  • 子コルーチンの終了を待つ

主な用途は次の通りです。

  • main 関数
  • コルーチンのテスト
  • 同期コードとコルーチンの橋渡し

ただし、アプリケーションの通常処理やAndroidのUIスレッドでは 使用を避けるべき関数でもあります。

以上、KotlinのrunBlockingについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次