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の特徴

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

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

例えば launchasync は非同期で実行されます。

しかし 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)
}

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

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スレッドで処理を行う

という動作になります。

runBlockingの主な用途

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

main関数

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

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

テストコード

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

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

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

同期コードとの橋渡し

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をコピーしました!
目次