runBlocking — Kotlin의 Coroutine Builder로, 전달된 코루틴이 완료될 때까지 현재 스레드를 블록합니다. launch와 async와 달리, 이것은 suspend 함수가 아니기 때문에 일반 (blocking) 코드에서 호출할 수 있습니다. JetBrains 문서, 2024에 따르면, runBlocking은 동기와 비동기 세계 사이의 교량 역할을 하여 main 함수와 테스트에서 코루틴을 실행할 수 있게 합니다.
주요 포인트
runBlocking은 새로운 CoroutineScope를 생성하고 전달된 코루틴을 실행하여 완전히 완료될 때까지 현재 스레드를 블록하는 Kotlin 함수입니다. 다른 모든 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")
}
출력은 세 개의 println 문이 모두 동일한 스레드에서 실행됨을 보여줍니다. runBlocking은 스레드를 전환하지 않고 event-loop를 사용하여 단일 스레드 내에서 협력적 멀티태스킹을 조직합니다.
runBlocking은 세 가지 시나리오에서 정당화됩니다: 콘솔 애플리케이션의 main() 입구 점, suspend 함수의 단위 테스트, 그리고 교량 — callback 기반 또는 blocking 라이브러리에서 suspend 코드 호출. 프로덕션 Android 코드에서는 메인 스레드에서의 사용이 엄격히 금지됩니다.
| 시나리오 | 적용 가능성 | 위험 |
|---|---|---|
| main() 콘솔 애플리케이션의 | 가능 | 없음 — 입구 점이며, 스레드가 UI를 블록하지 않음 |
| JUnit 테스트 | 가능 | 최소 — 테스트는 정의상 동기 |
| Android UI 스레드 | 불가 | ANR, 래그, 인터페이스 먹통 |
| Callback → Coroutine | 가능, 주의해서 | 긴 작업에서 스레드 풀 블록 |
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 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.