runBlocking — その概要、ブロックコンストラクタの橋渡しと仕組み

著者: IT Sectr 公開日: 2026-06-22 読了時間: 7 分

runBlocking — Kotlin の Coroutine Builder で、渡されたコルーチンが完了するまで現在のスレッドをブロックします。launch や async とは異なり、これは suspend 関数ではなく、普通の (blocking) コードから呼び出すことができます。JetBrains ドキュメント, 2024 によると、runBlocking は同期と非同期の世界を繋ぐ橋渡しとして、main 関数やテストからコルーチンを実行できるようにします。

メインポイント

  • runBlocking — CoroutineScope を作成しコルーチンの完了を待つブロックコンストラクタ
  • スレッドブロック — runBlocking はコルーチンとそのすべての子コルーチンが完了するまで現在のスレッドを保持する
  • エントリポイント — main(), JUnit テスト、blocking と async コードの橋渡し
  • Android のメインスレッドでは禁止 — UI スレッドで runBlocking を呼ぶと ANR が発生
  • 代替手段 — Android では lifecycleScope, viewModelScope, TestCoroutineDispatcher

runBlocking とは?

runBlocking は Kotlin の関数で、新しい CoroutineScope を作成し、渡されたコルーチンを実行し、完全に完了するまで現在のスレッドをブロックします。他のすべての Coroutine Builder とは異なり、runBlocking は suspend 関数ではなく、普通の同期コードから呼び出すことができます。runBlocking のシグネチャは CoroutineContext と suspend ブロックを受け取り、T 型の結果を返します。

kotlin
public fun <T> runBlocking(
    context: CoroutineContext = EmptyCoroutineContext,
    block: suspend CoroutineScope.() -> T
): T

runBlocking は現在のスレッドで新しい event-loop を始動します。コルーチンが suspend 関数(例えば delay() や await())を呼ぶと、runBlocking はスレッドをブロックし、一旦停止されたコルーチンが再開されるまで同じスレッド上で他の予定されたコルーチンを実行します。これが 協力的ブロック です — スレッドはアイドルではなく、他のコルーチンを処理します。

runBlocking の仕組み

runBlocking の内部メカニズムは event-loop に基づいています: suspend 関数が呼ばれると、runBlocking は現在のブロックの実行を一時停止し、キューから他のコルーチンを実行します。suspend 関数が完了すると、実行が再開されます。このサイクルはすべてのコルーチンが終了するまで続きます。

Event-loop の内部構造

runBlocking はコルーチンを実行するために独自のシングルスレッドプールを使用します。Dispatchers.IO や Default とは異なり、runBlocking はスレッドを切り替えません — すべてのコルーチンを現在のスレッドで処理し、実行を交叉させます。これは同じスレッドでの実行を保証する 唯一のコンストラクタ です。

kotlin
fun main() {
    val threadName = Thread.currentThread().getName()
    println("runBlocking の前: $threadName")

    val result = runBlocking {
        println("runBlocking の中: ${Thread.currentThread().getName()}")
        delay(500L)
        "Done"
    }

    println("runBlocking の後: $result")
}

出力は、すべての 3 つの println 文が同じスレッドで実行されることを示します。runBlocking はスレッドを切り替えるのではなく、event-loop を使用して単一スレッド内で協力的マルチタスクを組織します。

runBlocking を使うべき場合

runBlocking は 3 つのシナリオで証明されています: コンソールアプリケーションの main() エントリポイント、suspend 関数の単体テスト、および橋渡し — callback ベースまたは blocking ライブラリからの suspend コードの呼び出し。プロダクション Android コードでは、メインスレッドでの使用は 嚴重に禁止されています。

シナリオ適用性リスク
main() コンソールアプリのなし — エントリポイントであり、UI をブロックしない
JUnit テスト最小 — テストは定義上同期
Android UI スレッド不可ANR、ラグ、インタフェースのフリーズ
Callback → コルーチン可、注意して長時間オペレーションでのスレッドプールのブロック

Android テストの場合は、runBlocking の代わりに kotlinx-coroutines-test の TestDispatcher を使用します。これにより、時間制御、自動クリーンアップ、テストのアイソレーションが可能になります。

runBlocking の代替手段

大塕のシナリオでは、runBlocking は非同期代替手段で置き換えることができ、そうするべきです。Android では、viewModelScope、lifecycleScope、または適切なディスパッチャを持つ CoroutineScope がそれにあたります。テストには TestCoroutineDispatcher と runTest を使用します。

kotlin
    // 悪い: Android メインスレッドでの runBlocking
runBlocking(Dispatchers.Main) {
    val result = networkApi.fetchData()
    textView.setText(result)
}

// 良い: lifecycleScope
lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
    textView.setText(result)
}

テストの場合の代替は、kotlinx-coroutines-test の runTest です。これは仮想時間を持つ TestCoroutineScope を作成し、実際の待ち時間なしでディレイをテストできます。これによりテストが高速化され、一意的になります。

runBlocking の使用例

最もよくあるシナリオは、suspend 関数のテストです。テストでの runBlocking により、アーキテクチャを変えることなく、コルーチンの結果を同期的に待つことができます。二つ目のシナリオは、callback API を持つライブラリで、runBlocking を通じて blocking コンテキストから suspend 関数が呼ばれます。

kotlin
// runBlocking で suspend 関数をテスト
class RepositoryTest {
    @Test
    fun `fetchUser returns correct data`() {
        val repository = UserRepository(FakeApi())

        val result = runBlocking {
            repository.fetchUser("123")
        }

        assertEquals("John", result.name)
        assertEquals("john@test.com", result.email)
    }
}

callback と suspend の世界を繋ぐ場合は、callbacks の代わりに runBlocking と組み合わせて CompletableDeferred を使用します — これにより非同期オペレーションのチェーンが簡素化され、コードの可読性が向上します。

不正な使用の危険性

runBlocking の不適切な使用は、blocking アプローチからコルーチンへの移行時によくある趣問のひとつです。主な問題: Android のメインスレッドでの呼び出し、runBlocking の立ち入り、非同期関数内での使用、および runBlocking を通じた長時間オペレーションの実行です。

  • ANR — Android のメインスレッドでの runBlocking が UI のレンダリングを 5 秒以上ブロック
  • デッドロック — 同じスレッド上のコルーチン内のネストされた runBlocking が相互ブロックを引き起こす
  • ディスパッチャの混乱 — バックグラウンドスレッドでの runBlocking 内の Dispatchers.Main には Looper がなく、例外が発生
  • メモリリーク — Activity/Fragment が破壊されても runBlocking は自動的にキャンセルされない

黄金ルール: runBlocking は 橋渡しであって、置き換えではありません。blocking と non-blocking の世界を繋ぐためだけ使用します。その他のすべてのタスクには、launch、async、または lifecycleScope を使用します。

よくある質問

runBlocking がスレッドをブロックするのに、他のコンストラクタはブロックしないのはなぜ?

runBlocking は、suspend 関数ではない唯一のコンストラクタです。現在のスレッドで event-loop を始動し、すべてのコルーチンが完了するまで制御を返さないのです。launch と async は即時に制御を返し、コルーチンをバックグラウンドで実行します。

Android の ViewModel で runBlocking を使用できますか?

推奨されません。ViewModel には、コルーチンを自動的に管理し、破壊時にキャンセルする組み込みの viewModelScope があります。ViewModel での runBlocking はスレッドをブロックし、ライフサイクルのキャンセルに応答しません。

単体テストで runBlocking の代わりに何を使うべきですか?

kotlinx-coroutines-test ライブラリの runTest を使用します。これは、仮想時間制御、自動キャンセル、一意的な実行を持つ TestCoroutineScope を提供します。

runBlocking における event-loop とは?

Event-loop は runBlocking 内のイベント処理サイクルです。コルーチンが一時停止すると(例: delay())、event-loop は同じスレッド上で他の実行可能なコルーチンの実行に切り替えます。これは、スレッド切り替えなしでマルチタスクの仮想を作り出します。

runBlocking の中で runBlocking を呼ぶとどうなりますか?

同じスレッド上での ネストされた runBlocking はデッドロックを引き起こします — 外側のブロックが内側を待ち、内側は外側が終了するまで開始できません。異なるスレッドでは許容されますが、デバッグの複雑さから強く推奨されません。

まとめ

  • runBlocking — blocking Coroutine Builder、blocking と async コードの橋渡し
  • Event-loop runBlocking は切り替えなしで単一スレッド上で協力的にコルーチンを処理
  • 許容されるシナリオ — main(), JUnit テスト、callback ライブラリからの橋渡し
  • 禁止されるシナリオ — Android UI スレッド、ネスト呼び出し、長時間オペレーション
  • 代替手段 — lifecycleScope、viewModelScope、テスト用 runTest
  • ANR リスク — メインスレッドでの runBlocking が 5 秒後にアプリをフリーズ
  • プロダクション Android コードでは、async コンストラクタを使用 — runBlocking は UI 用に設計されていません

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください