본문 바로가기

IT/Android

더 일찍 알았더라면 좋았을 Kotlin 코루틴의 숨겨진 5가지 비밀

728x90
반응형
# 더 일찍 알았더라면 좋았을 Kotlin 코루틴의 숨겨진 5가지 비밀 Kotlin 코루틴은 비동기 프로그래밍을 위한 강력한 도구입니다. 하지만 많은 개발자들이 놓치고 있는 숨겨진 기능들이 있습니다. 이 글에서는 코루틴을 더 효과적으로 사용할 수 있는 5가지 비밀을 공유합니다. ## 1. 코루틴 스코프는 계층적이다 많은 개발자들이 코루틴 스코프가 단순히 코루틴을 실행하는 컨테이너라고 생각하지만, 실제로는 **계층적 구조**를 갖고 있습니다.
val parentScope = CoroutineScope(Dispatchers.Main)

parentScope.launch {
    // 부모 코루틴
    launch {
        // 자식 코루틴 1
        delay(1000)
        println("자식 1 완료")
    }

    launch {
        // 자식 코루틴 2
        delay(2000)
        println("자식 2 완료")
    }

    println("부모 코루틴 시작")
    delay(3000)
    println("부모 코루틴 완료")
}
**핵심 포인트:** - 부모 코루틴은 모든 자식 코루틴이 완료될 때까지 기다립니다 - 부모가 취소되면 모든 자식도 자동으로 취소됩니다 - 자식에서 예외가 발생하면 부모까지 전파됩니다 ## 2. launch vs async의 진짜 차이점 많은 개발자들이 `launch`와 `async`의 차이를 단순히 "반환값이 있나 없나"로만 이해하지만, 실제 차이는 더 깊습니다.
// launch: "fire and forget" 방식
launch {
    val result = performNetworkCall()
    updateUI(result)
}

// async: 결과가 필요한 경우
val deferred = async {
    performNetworkCall()
}
val result = deferred.await() // 결과를 기다림
**중요한 차이점:** - `launch`는 Job을 반환하고, `async`는 Deferred를 반환합니다 - `async`로 시작된 코루틴에서 예외가 발생하면 `await()`를 호출할 때까지 예외가 숨겨집니다 - 병렬 처리가 필요할 때는 `async`를 사용하세요
// 병렬 처리 예제
suspend fun fetchUserData(): UserData {
    val profileDeferred = async { fetchUserProfile() }
    val settingsDeferred = async { fetchUserSettings() }

    return UserData(
        profile = profileDeferred.await(),
        settings = settingsDeferred.await()
    )
}
## 3. Dispatcher의 숨겨진 힘 많은 개발자들이 `Dispatchers.Main`, `Dispatchers.IO`, `Dispatchers.Default`만 사용하지만, 더 세밀한 제어가 가능합니다.
// 커스텀 디스패처 생성
val customDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()

// 특정 스레드 수 제한
val limitedDispatcher = Dispatchers.IO.limitedParallelism(2)

// CPU 집약적 작업을 위한 최적화
val cpuIntensiveDispatcher = Dispatchers.Default.limitedParallelism(
    Runtime.getRuntime().availableProcessors()
)
**실용적인 팁:** - 파일 I/O 작업이 많다면 `Dispatchers.IO.limitedParallelism()`을 사용하여 스레드 수를 제한하세요 - 데이터베이스 연결처럼 제한된 리소스를 사용할 때는 커스텀 디스패처를 만드세요 ## 4. withContext vs async/await의 미묘한 차이 둘 다 컨텍스트를 변경하는 데 사용되지만, 성능과 메모리 사용량에서 차이가 있습니다.
// withContext 사용 (권장)
suspend fun loadData(): String {
    return withContext(Dispatchers.IO) {
        // I/O 작업
        "데이터"
    }
}

// async/await 사용 (불필요한 오버헤드)
suspend fun loadDataAsync(): String {
    return async(Dispatchers.IO) {
        // I/O 작업
        "데이터"
    }.await()
}
**언제 무엇을 사용할까?** - **withContext**: 단순히 컨텍스트를 변경하고 결과를 반환할 때 - **async/await**: 여러 작업을 병렬로 실행할 때 ## 5. 코루틴의 취소는 협력적이다 코루틴의 취소는 강제적이 아니라 **협력적**입니다. 이는 많은 버그의 원인이 됩니다.
// 잘못된 예: 취소가 되지 않는 코루틴
launch {
    while (true) {
        // CPU 집약적 작업
        heavyComputation()
    }
}

// 올바른 예: 취소를 확인하는 코루틴
launch {
    while (isActive) { // isActive 확인
        heavyComputation()
    }
}

// 또는 ensureActive() 사용
launch {
    while (true) {
        ensureActive() // 취소 상태 확인
        heavyComputation()
    }
}
**취소를 지원하는 함수들:** - `delay()` - `yield()` - `withContext()` - 모든 suspend 함수들 **취소 처리 베스트 프랙티스:**
launch {
    try {
        while (isActive) {
            // 작업 수행
            heavyWork()
            yield() // 다른 코루틴에게 실행 기회 제공
        }
    } catch (e: CancellationException) {
        // 정리 작업
        cleanup()
        throw e // 다시 던져야 함
    }
}
## 마무리 이러한 코루틴의 숨겨진 비밀들을 이해하면: 1. **더 안정적인 앱**: 예외 처리와 취소 처리를 올바르게 할 수 있습니다 2. **더 나은 성능**: 적절한 디스패처와 컨텍스트 전환을 선택할 수 있습니다 3. **더 깔끔한 코드**: 코루틴의 계층 구조를 활용하여 체계적으로 코드를 작성할 수 있습니다 코루틴은 단순해 보이지만 그 내부에는 많은 복잡성이 숨어있습니다. 이러한 개념들을 이해하고 활용한다면, 더 견고하고 효율적인 비동기 코드를 작성할 수 있을 것입니다. --- **태그:** kotlin, coroutines, android, programming, asynchronous, suspendfunction, kotlincoroutines, androiddeveloper, kotlintips, concurrency *이 글이 도움이 되었다면 공유해 주세요! 코루틴에 대한 더 많은 팁과 트릭을 알고 계시다면 댓글로 공유해 주시기 바랍니다.*
728x90
반응형