Coroutine Builder — 코루틴을 생성하고 실행하며 실행 방식을 정의하는 Kotlin Coroutines 함수입니다. launch, async, runBlocking, produce 빌더는 백그라운드 작업부터 결과를 반환하는 병렬 계산까지 다양한 시나리오를 다룹니다. JetBrains, 2024에 따르면 Coroutine Builder는 코루틴 모델의 기초로서 구조적 동시성과 라이프사이클 관리를 제공합니다.
핵심 포인트
Coroutine Builder는 CoroutineScope와 suspend 블록을 받아 새로운 코루틴을 생성하고 시작하는 Kotlin 확장 함수입니다. 각 빌더는 결과 반환 여부, 스레드 차단 여부, 비동기 방식 등 코루틴의 실행 방식을 정의합니다. 빌더는 언어의 코루틴 모델에 대한 진입점입니다.
모든 빌더는 CoroutineScope를 통해 작동하며, 자식 코루틴의 라이프사이클을 관리합니다. 스코프가 취소되면 해당 스코프를 통해 시작된 모든 코루틴이 자동으로 취소됩니다 — 이것이 구조적 동시성의 원칙입니다. 이 접근 방식은 코루틴 누수를 방지하고 예측 가능한 종료를 보장합니다.
import kotlinx.coroutines.*
fun main() = runBlocking {
// 빌더는 CoroutineScope 내에서 실행됩니다
val job = launch {
delay(1000L)
println("세계!")
}
println("안녕하세요,")
job.join()
}
Kotlin은 네 가지 내장 코루틴 빌더를 제공합니다: launch, async, runBlocking, produce. 각각 고유한 반환 유형과 적용 영역이 있습니다. Android 모바일 개발의 주요 빌더는 launch와 async입니다 — 이들은 비차단 방식으로 작동하며 아키텍처 컴포넌트와 통합됩니다.
| 빌더 | 반환 유형 | 스레드 차단 | 시나리오 |
|---|---|---|---|
| launch | Job | 아니오 | Fire-and-forget 작업 |
| async | Deferred<T> | 아니오 | 병렬 계산 |
| runBlocking | T | 예 | 테스트, main 함수 |
| produce | ReceiveChannel<E> | 아니오 | 스트리밍 (비권장) |
각 빌더는 추가 매개변수를 허용합니다: CoroutineStart (시작 전략), CoroutineContext (디스패처, 예외), 명명된 코드 블록. 기본적으로 코루틴은 즉시 시작됩니다 (CoroutineStart.DEFAULT).
launch는 Android 개발에서 가장 많이 사용되는 빌더입니다. 결과를 반환하지 않는 코루틴을 시작하고 라이프사이클 관리를 위한 Job 객체를 반환합니다. 데이터베이스 저장, 분석 전송, UI 업데이트와 같이 부수 효과만 필요한 작업에 이상적인 선택입니다.
launch 빌더는 CoroutineScope, 선택적 CoroutineContext 및 suspend 블록을 허용합니다. 반환된 Job은 코루틴을 취소하거나, 완료를 기다리거나, 상태를 확인할 수 있게 합니다.
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
val job: Job = scope.launch(CoroutineStart.LAZY) {
val data = fetchFromNetwork()
saveToDatabase(data)
}
job.start()
job.join()
CoroutineStart.LAZY 매개변수는 start() 또는 join()이 명시적으로 호출될 때까지 실행을 지연시킵니다. 이는 지연 초기화 및 조건부 실행에 유용합니다. 표준 즉시 실행의 경우 CoroutineStart.DEFAULT를 사용하거나 매개변수를 생략합니다.
async는 Deferred<T> — 결과의 비동기 약속을 반환하는 빌더입니다. await()를 호출하면 스레드를 차단하지 않고 결과를 얻을 때까지 코루틴을 일시 중단합니다. 이는 Kotlin 코루틴에서 병렬 작업을 위한 기본 메커니즘입니다.
async는 여러 독립적인 작업을 동시에 실행해야 할 때 특히 효과적입니다. suspend 함수의 순차적 호출과 달리 async는 코루틴을 병렬로 시작하여 총 실행 시간을 줄입니다.
suspend fun fetchUserData(): UserData {
val deferred1 = CoroutineScope(Dispatchers.IO).async { api.getProfile() }
val deferred2 = CoroutineScope(Dispatchers.IO).async { api.getSettings() }
val deferred3 = CoroutineScope(Dispatchers.IO).async { api.getNotifications() }
return UserData(
profile = deferred1.await(),
settings = deferred2.await(),
notifications = deferred3.await()
)
}
Deferred는 Job을 상속하므로 async는 취소, 완료 대기, 예외 처리 등 모든 라이프사이클 작업을 지원합니다. 스코프가 취소되면 자식 Deferred 코루틴이 자동으로 취소됩니다.
runBlocking은 코루틴이 완료될 때까지 현재 스레드를 차단하는 유일한 빌더입니다. 새로운 CoroutineScope를 생성하고 전달된 코루틴을 시작하여 호출 스레드를 차단합니다. main() 진입점, 테스트 및 blocking 코드와의 통합에 사용됩니다.
runBlocking은 세 가지 시나리오에서 정당화됩니다: 애플리케이션 진입점 (main), suspend 함수의 단위 테스트, suspend를 사용할 수 없는 콜백 기반 라이브러리와의 통합. 프로덕션 Android 코드에서 메인 스레드에 runBlocking을 사용하는 것은 강력히 권장되지 않습니다.
class CoroutineTest {
@Test
fun `test suspend function`() = runBlocking {
val result = mySuspendFunction()
assertEquals("expected", result)
}
}
테스트의 경우 runBlocking 대신 TestCoroutineDispatcher와 함께 kotlinx-coroutines-test를 사용하는 것이 좋습니다 — 이를 통해 시간 제어가 가능하고 테스트 환경에서 차단을 방지할 수 있습니다.
Coroutine Builder의 선택은 반환 결과와 실행 시나리오에 따라 다릅니다. 데이터 반환이 필요하지 않은 작업에는 launch를 사용합니다. 비동기 작업의 결과가 필요하면 async를 사용합니다. runBlocking은 브리징에만 사용하고 반응형 스트림에는 produce를 Flow로 대체하세요.
Kotlin Coroutines를 사용하는 Android 프로젝트에서 주요 빌더 쌍은 launch와 async입니다. launch는 ViewModel과 UseCases에서 코루틴을 시작하는 데 사용되고, async는 네트워크 또는 데이터베이스에 대한 병렬 요청에 사용됩니다. 최신 라이브러리 (Ktor, Room)는 이미 suspend 함수를 지원하므로 async의 직접 사용 필요성이 최소화됩니다.
자주 묻는 질문
launch는 Job을 반환하고 실행 결과를 반환하지 않는 반면, async는 Deferred<T>를 반환하며 await()를 통해 결과를 얻을 수 있습니다. launch는 fire-and-forget 작업에, async는 데이터를 반환하는 작업에 사용됩니다.
권장되지 않습니다. 메인 스레드에서 runBlocking은 ANR을 유발하고 UI를 차단합니다. Activity와 Fragment 내에서 lifecycleScope.launch를 사용하세요 — 차단 없는 내장 솔루션입니다.
launch 빌더는 Job 객체를 반환하며, 코루틴의 라이프사이클 제어 (취소, 완료 대기, 상태 확인)를 가능하게 합니다.
Deferred<T>는 async 빌더가 반환하는 결과의 비동기 약속입니다. Job을 상속하며 결과를 얻기 위한 await(), 비차단 접근을 위한 getCompleted(), 예외 확인을 위한 getCompletionExceptionOrNull() 메서드를 추가합니다.
CoroutineStart.LAZY 매개변수를 사용합니다: scope.launch(start = CoroutineStart.LAZY) { ... }. 그런 다음 실제 실행을 위해 job.start() 또는 job.join()을 호출합니다. 이는 지연 초기화 및 조건부 코루틴 실행에 유용합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.