runBlocking — Kotlin の Coroutine Builder で、渡されたコルーチンが完了するまで現在のスレッドをブロックします。launch や async とは異なり、これは suspend 関数ではなく、普通の (blocking) コードから呼び出すことができます。JetBrains ドキュメント, 2024 によると、runBlocking は同期と非同期の世界を繋ぐ橋渡しとして、main 関数やテストからコルーチンを実行できるようにします。
メインポイント
runBlocking は Kotlin の関数で、新しい CoroutineScope を作成し、渡されたコルーチンを実行し、完全に完了するまで現在のスレッドをブロックします。他のすべての Coroutine Builder とは異なり、runBlocking は suspend 関数ではなく、普通の同期コードから呼び出すことができます。runBlocking のシグネチャは CoroutineContext と suspend ブロックを受け取り、T 型の結果を返します。
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking は現在のスレッドで新しい event-loop を始動します。コルーチンが suspend 関数(例えば delay() や await())を呼ぶと、runBlocking はスレッドをブロックし、一旦停止されたコルーチンが再開されるまで同じスレッド上で他の予定されたコルーチンを実行します。これが 協力的ブロック です — スレッドはアイドルではなく、他のコルーチンを処理します。
runBlocking の内部メカニズムは event-loop に基づいています: suspend 関数が呼ばれると、runBlocking は現在のブロックの実行を一時停止し、キューから他のコルーチンを実行します。suspend 関数が完了すると、実行が再開されます。このサイクルはすべてのコルーチンが終了するまで続きます。
runBlocking はコルーチンを実行するために独自のシングルスレッドプールを使用します。Dispatchers.IO や Default とは異なり、runBlocking はスレッドを切り替えません — すべてのコルーチンを現在のスレッドで処理し、実行を交叉させます。これは同じスレッドでの実行を保証する 唯一のコンストラクタ です。
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 は 3 つのシナリオで証明されています: コンソールアプリケーションの main() エントリポイント、suspend 関数の単体テスト、および橋渡し — callback ベースまたは blocking ライブラリからの suspend コードの呼び出し。プロダクション Android コードでは、メインスレッドでの使用は 嚴重に禁止されています。
| シナリオ | 適用性 | リスク |
|---|---|---|
| main() コンソールアプリの | 可 | なし — エントリポイントであり、UI をブロックしない |
| JUnit テスト | 可 | 最小 — テストは定義上同期 |
| Android UI スレッド | 不可 | ANR、ラグ、インタフェースのフリーズ |
| Callback → コルーチン | 可、注意して | 長時間オペレーションでのスレッドプールのブロック |
Android テストの場合は、runBlocking の代わりに kotlinx-coroutines-test の TestDispatcher を使用します。これにより、時間制御、自動クリーンアップ、テストのアイソレーションが可能になります。
大塕のシナリオでは、runBlocking は非同期代替手段で置き換えることができ、そうするべきです。Android では、viewModelScope、lifecycleScope、または適切なディスパッチャを持つ CoroutineScope がそれにあたります。テストには TestCoroutineDispatcher と runTest を使用します。
// 悪い: 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 を作成し、実際の待ち時間なしでディレイをテストできます。これによりテストが高速化され、一意的になります。
最もよくあるシナリオは、suspend 関数のテストです。テストでの runBlocking により、アーキテクチャを変えることなく、コルーチンの結果を同期的に待つことができます。二つ目のシナリオは、callback API を持つライブラリで、runBlocking を通じて blocking コンテキストから suspend 関数が呼ばれます。
// 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 を通じた長時間オペレーションの実行です。
黄金ルール: runBlocking は 橋渡しであって、置き換えではありません。blocking と non-blocking の世界を繋ぐためだけ使用します。その他のすべてのタスクには、launch、async、または lifecycleScope を使用します。
よくある質問
runBlocking は、suspend 関数ではない唯一のコンストラクタです。現在のスレッドで event-loop を始動し、すべてのコルーチンが完了するまで制御を返さないのです。launch と async は即時に制御を返し、コルーチンをバックグラウンドで実行します。
推奨されません。ViewModel には、コルーチンを自動的に管理し、破壊時にキャンセルする組み込みの viewModelScope があります。ViewModel での runBlocking はスレッドをブロックし、ライフサイクルのキャンセルに応答しません。
kotlinx-coroutines-test ライブラリの runTest を使用します。これは、仮想時間制御、自動キャンセル、一意的な実行を持つ TestCoroutineScope を提供します。
Event-loop は runBlocking 内のイベント処理サイクルです。コルーチンが一時停止すると(例: delay())、event-loop は同じスレッド上で他の実行可能なコルーチンの実行に切り替えます。これは、スレッド切り替えなしでマルチタスクの仮想を作り出します。
同じスレッド上での ネストされた runBlocking はデッドロックを引き起こします — 外側のブロックが内側を待ち、内側は外側が終了するまで開始できません。異なるスレッドでは許容されますが、デバッグの複雑さから強く推奨されません。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。