3 minute read

핵심 요약

  • 코루틴은 멈출 수 있는 함수
  • 스코프는 코루틴들의 놀이터
  • 디스패처는 어느 쓰레드에서 실행할지 결정
  • async/await는 여러 작업을 동시에 처리
  • 채널은 코루틴들 간의 소통 도구

1. 코루틴 Coroutine

원리
일반적인 함수는 시작하면 끝날 때까지 멈출 수 없지만,
코루틴은 중간에 멈췄다가 나중에 다시 시작할 수 있는 특별한 함수입니다.
마치 게임을 하다가 일시정지하고 나중에 이어서 하는 것과 비슷합니다.

사용법
suspend 키워드로 함수를 만들고,
runBlocking이나
launch로 실행합니다.

suspend fun fetchData() {
    delay(1000) // 1초 기다림 (멈춤)
    println("데이터 가져오기 완료")
}

runBlocking {
    fetchData()
}

2. 코루틴 스코프 Coroutine Scope

원리
코루틴들이 활동할 수 있는 “놀이터” 같은 개념입니다.
놀이터가 없어지면 그 안의 모든 코루틴도 함께 정리됩니다.
메모리 누수를 방지하는 안전장치 역할을 합니다.

사용법
CoroutineScope를 만들어서
그 안에서 코루틴을 실행합니다.

val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
    // 코루틴 작업
}

3. 디스패처(Dispatcher)

원리
코루틴이 어느 쓰레드에서 실행될지 결정하는 “교통 관제소” 같은 역할입니다.
UI 작업은 메인 쓰레드에서,
무거운 계산은 백그라운드 쓰레드에서 실행하도록 배치합니다.

사용법
Dispatchers.Main
Dispatchers.IO
Dispatchers.Default 등을 사용합니다.

launch(Dispatchers.IO) {
    // 파일 읽기, 네트워크 작업
}
launch(Dispatchers.Main) {
    // UI 업데이트
}

4. async와 await

원리
여러 작업을 동시에 시작하고 결과를 기다리는 방법입니다.
마치 여러 명의 친구에게 동시에 심부름을 시키고,
모든 친구가 돌아올 때까지 기다리는 것과 비슷합니다.

사용법: async로 작업을 시작하고, await()로 결과를 받습니다.

val job1 = async { fetchUserData() }
val job2 = async { fetchProductData() }

val userData = job1.await()
val productData = job2.await()

5. 채널(Channel)

원리
코루틴들 간에 데이터를 주고받는 “우체통” 같은 역할입니다.
한 코루틴이 데이터를 보내면 다른 코루틴이 받을 수 있습니다.
실시간으로 데이터를 전달할 때 유용합니다.

사용법
Channel을 만들고
send()receive()로 데이터를 주고받습니다.

val channel = Channel<String>()

launch {
    channel.send("안녕하세요")
}

launch {
    val message = channel.receive()
    println(message)
}

6. Flow

원리
데이터가 강물처럼 연속적으로 흘러오는 것을 처리하는 기술입니다.
실시간 채팅, 주식 가격 변동, 위치 정보 등 계속 변하는 데이터를 다룰 때 사용합니다.

사용법
flow { } 빌더로 만들고 collect로 데이터를 받습니다.

val dataFlow = flow {
    repeat(5) {
        emit("데이터 $it")
        delay(1000)
    }
}

dataFlow.collect { value ->
    println(value)
}

7. 예외 처리 (Exception Handling)

원리
코루틴에서 에러가 발생했을 때
전체 앱이 멈추지 않도록
안전하게 처리하는 방법입니다.
일반적인 try-catch와 비슷하지만 비동기 환경에서 더 복잡합니다.

사용법
CoroutineExceptionHandlersupervisorScope 사용

val handler = CoroutineExceptionHandler { _, exception ->
    println("에러 발생: ${exception.message}")
}

launch(handler) {
    // 에러가 발생할 수 있는 코드
}

8. 취소 처리 (Cancellation)

원리
더 이상 필요하지 않은 작업을 중단시키는 기능입니다.
사용자가 화면을 나가거나 앱을 종료할 때 진행 중인 작업을 정리해야 합니다.

사용법
cancel()로 취소하고, isActive로 상태 확인

val job = launch {
    repeat(1000) {
        if (!isActive) return@launch
        delay(100)
        // 작업 수행
    }
}

job.cancel() // 작업 취소

9. 코루틴 컨텍스트 (Coroutine Context)

원리
코루틴의 “신분증” 같은 개념입니다.
어느 쓰레드에서 실행되는지,
어떤 이름을 가지는지,
에러 처리는 어떻게 할지 등의 정보를 담고 있습니다.

사용법
여러 컨텍스트를 + 연산자로 조합

launch(Dispatchers.IO + CoroutineName("MyCoroutine")) {
    // IO 쓰레드에서 "MyCoroutine"이라는 이름으로 실행
}

10. 구조화된 동시성 (Structured Concurrency)

원리
부모-자식 관계를 가진 코루틴들이 서로 연결되어 있어서,
부모가 취소되면 자식들도 모두 자동으로 취소되는 구조입니다.
메모리 누수를 방지하는 핵심 원리입니다.

사용법
coroutineScopesupervisorScope 활용

coroutineScope {
    launch { /* 자식 코루틴 1 */ }
    launch { /* 자식 코루틴 2 */ }
    // 모든 자식이 완료될 때까지 기다림
}

11. 백프레셔 (Backpressure)

원리
데이터를 생산하는 속도가
소비하는 속도보다 빠를 때
발생하는 문제를 해결하는 기술입니다.

버퍼가 가득 차면 어떻게 할지 정하는 전략입니다.

사용법
Flow에서 buffer(), conflate(), collectLatest() 사용

flow.buffer(50) // 50개까지 버퍼링
    .collect { /* 처리 */ }

12. 콜드 스트림 vs 핫 스트림

원리

  • 콜드 스트림: 구독자가 있을 때만 데이터를 생산 (Flow)
  • 핫 스트림: 구독자가 없어도 계속 데이터를 생산 (Channel, StateFlow)

사용법
상황에 따라 적절한 타입 선택

// 콜드 스트림
val coldFlow = flow { emit(getData()) }

// 핫 스트림  
val hotChannel = Channel<String>()

이외 중요한 패턴들

13. withContext 패턴

suspend fun loadData(): String = withContext(Dispatchers.IO) {
    // 무거운 작업을 IO 쓰레드에서 실행
    "결과"
}

14. 리소스 정리 패턴

try {
    // 코루틴 작업
} finally {
    // 리소스 정리 (파일 닫기, 연결 해제 등)
}

15. 타임아웃 처리

withTimeout(5000) { // 5초 제한
    fetchData()
}

Coroutine을 통해
안드로이드 앱에서 복잡한 비동기 작업도 안전하고 효율적으로 처리할 수 있습니다.

Categories:

Updated: