나미래 Android 개발자

코틀린 coroutineScope 사용법과 예제 - 코루틴 스코프 함수 완벽 가이드 본문

안드로이드/Coroutine

코틀린 coroutineScope 사용법과 예제 - 코루틴 스코프 함수 완벽 가이드

Moimeme Futur 2025. 8. 7. 19:00
반응형

이 글은 코틀린 코루틴 마르친 모스카와의 ⟪코틀린 코루틴⟫ 책을 기반으로 작성하였습니다.

 

여러분은 코루틴 스코프 함수는 어떤 것들이 있고, 어떤 경우에 사용해야 하는지 질문을 받는다면 답을 어떻게 하실건가요?

만약 개발 중에 PR 리뷰로 또는 면접 질문으로 코루틴 스코프 함수를 왜 사용했냐는 질문을 받는다면 질문자를 납득할 수 있는 답변을 하실 수 있으신가요?

만약 답변하기 어렵다고 생각이 든다면 이 글을 읽고 나서는 자신있게 coroutineScope에 대해서 위와 같은 질문에 대해서 답변하실 수 있으실 겁니다.

코루틴 스코프 함수란?

먼저 기본 배경 지식으로 '코루틴 스코프 함수'에 대해서 간략하게 설명하겠습니다.

'코루틴 스코프 함수'는 코틀린에서 제공하는 코루틴 스코프를 만드는 중단 함수입니다.

'코루틴 스코프 함수'에는 coroutineScope, supervisorScope, withContext, withTimeout 이 있습니다.

참고로 중단 함수이기 때문에 코루틴 내에서 호출되어야 합니다.

이번 글에서는 '코루틴 스코프 함수' 중 기본이라고 생각되는 coroutineScope 함수에 대해서 이야기하도록 하겠습니다.

문제 상황

coroutineScope 함수를 설명하기 위해서 먼저 간단한 개발 요구사항을 이야기하겠습니다.

만약 여러분이라면 다음과 같은 상황에서 어떻게 개발을 할 것인지 생각해보시고 읽는다면 coroutineScope 함수를 이해하는 데 도움이 될 것입니다.

"여러 개의 엔드포인트에서 데이터를 동시에 얻어야 하는 중단 함수에서 코루틴을 효과적으로 사용하려면 어떻게 해야할까요?"

생각해 보셨나요?

기존 해결 방법들의 한계

많은 사람들이 코루틴 스코프 함수가 나오기 전에 아래 3가지 방법으로 개발을 했다고 합니다.

  1. 중단 함수에서 중단 함수 호출하기
  2. 코루틴 스코프를 파라미터로 전달하기
  3. 코루틴 스코프 확장함수 만들기

(혹시 여러분들도 위 3가지 방법을 떠올리셨다면 이 다음부터 전달할 내용이 큰 도움이 될 것입니다.)

위 3가지 방법에는 문제점 또는 위험성을 갖고 있습니다.

1. 중단 함수에서 중단 함수 호출하기의 문제점

[1. 중단 함수에서 중단 함수 호출하기]에서는 코틀린 코루틴 개념을 사용하는 의미가 없어집니다.

중단 함수를 사용했지만 요구사항 중 "동시에"를 충족하지 못합니다.

중단 함수에서 중단 함수를 2개 사용했다고 생각해보면 첫 번째 중단 함수에서 데이터를 얻는 동안 코루틴이 중단되었기 때문에 두 번째 중단 함수에서 데이터를 "동시에" 얻지 못합니다.

두 번째 중단 함수는 첫 번째 중단 함수가 재개 된 이후에 실행이 될 수 있는 것이죠.

즉, 비동기적으로 데이터들을 받아오는 것이 아니라 "동기적(순차적)"으로 데이터를 받아오게 됩니다.

그러니 중단 함수에서 중단 함수를 순차적으로 호출하는 것은 요구사항에 부합하지 않다는 게 명확해졌죠.

2. 스코프 참조 방식의 위험성

[2. 코루틴 스코프를 파라미터로 전달하기], [3. 코루틴 스코프 확장함수 만들기] 2가지는 동일한 위험성을 가지고 있습니다.

이 2가지 방법은 첫 번째 방법보다는 나은 방법이지만 다음과 같은 위험성을 갖고 있습니다.

  1. 스코프가 참조됨으로써 cancel()을 이용해서 의도와 다르게 코루틴이 취소됩니다.
  2. 전달받은 코루틴 스코프에서 예외가 발생하는 경우 의도와 다르게 코루틴이 종료됩니다.

개발을 혼자하고 모든 것을 기억하고 파악하고 코드를 작성하는 개발자라면 사실 문제가 되지 않을 수 있습니다.

하지만 사람은 완벽하지 않으며 개발은 혼자서할 수 없는 협업 활동입니다.

만약 여러분이 중단 함수를 만들 때, 코루틴 스코프를 참조할 수 있도록 코드를 작성했고 의도 하에 내부에서 cancel() 하거나 중단 함수 내부에서 예외가 발생하면 참조한 코루틴 스코프에 예외가 발생하도록 만들었다고 상황을 가정해보겠습니다.

하지만 여러분이 아닌 다른 개발자가 코루틴 스코프 참조가 가능한 중단 함수를 포함한 코루틴 스코프에서 코드를 수정해야할 일이 있다면, 그리고 그 개발자가 여러분만큼 꼼꼼하지 않은 개발자라면 여러분이 만든 중단 함수 내부를 들어가지 않고 본인의 업무에 해당하는 코드만 추가할 수 있습니다.

그리고 테스트 중에 개발자 의도와 다르게 코루틴이 죽는다면 그 사람은 분명 의아해하고 원인을 찾는데 어려움을 겪을 것입니다.

개발자는 협업이 필수인 시대에서 이런 위험성을 가지고 가는 것은 절대 좋지 않습니다.

그러기 때문에 위 2가지 방법으로 요구사항을 충족한 코드를 작성할 수 있지만 위험성을 갖는 코드를 생산하게 됩니다.

coroutineScope의 등장

그렇다면 어떻게 해결해야 할까요?

맞습니다. coroutineScope 함수를 사용하면 됩니다.

coroutineScope는 어떤 특성을 가지고 있어서 위와 같은 요구사항에 적합한 것일까요?

 
 
suspend fun <R> coroutineScope(
    block: suspend CoroutineScope.() -> R
): R

coroutineScope의 핵심 특성

  1. coroutineScope는 코루틴 스코프를 생성하는 중단 함수입니다.
    • 생성된 코루틴 스코프의 코루틴 컨텍스트는 함수를 호출한 코루틴 컨텍스트를 상속 받습니다.
    • 단, 코루틴 컨텍스트 중 Job은 일반 Job으로 override 합니다. (새로운 Job을 만듭니다.)
  2. coroutineScope는 파라미터로 전달받은 함수가 생성한 값을 반환합니다.

coroutineScope의 핵심 특징

coroutineScope는 위와 같은 2가지 특성을 가지고 있어서 다음과 같은 특징을 갖습니다.

1. 구조적 동시성

  • 코루틴 스코프를 생성하기 때문에 coroutineScope 내부에서 코루틴 빌더(launch, async)로 코루틴을 생성할 수 있습니다.
  • Job을 일반 Job으로 override 하기 때문에 코틀린 코루틴의 장점인 "구조적 동시성"을 갖습니다. 다시 말해서 coroutineScope 내부에서 생성한 자식 코루틴이 취소되는 경우, 부모인 coroutineScope의 Job에 전파되며 부모인 coroutineScope가 취소된 경우에도 자식 코루틴 모두를 취소합니다.

2. 예외 전파

  • coroutineScope는 중단 함수이기 때문에 coroutineScope에서 발생한 취소 또는 예외가 부모(호출한 코루틴)의 Job으로 전파되지 않습니다. (예외는 함수 호출부로 throw 됩니다.)

3. 컨텍스트 상속

  • 부모의 컨텍스트를 상속받되, 새로운 Job을 생성하여 안전한 스코프를 만듭니다.

실제 사용 예시

그렇다면 제가 질문했던 요구사항("여러 개의 엔드포인트에서 데이터를 동시에 얻어야 하는 중단 함수에서 코루틴을 효과적으로 사용하려면 어떻게 해야할까요?")을 충족하기 위해서 어떻게 사용할 수 있는지 코틀린 코드를 이용해서 보여드리겠습니다.

 
 

 

suspend fun getUserProfile(): UserProfileData = coroutineScope {
    // 동시에 실행되는 비동기 작업들
    val userDeferred = async { getUserData() }
    val postsDeferred = async { getUserPosts() }
    val followsDeferred = async { getUserFollows() }
    val followersDeferred = async { getUserFollowers() }
    
    // 모든 결과를 기다렸다가 객체 생성
    UserProfileData(
        user = userDeferred.await(),
        posts = postsDeferred.await(),
        follows = followsDeferred.await(),
        followers = followersDeferred.await()
    )
}

어떠신가요? 위 코드가 어떻게 요구사항을 충족하는지 머릿속에 그려지시나요?

이 코드에서는 4개의 API 호출이 동시에 시작되고, 모든 결과가 완료되면 UserProfileData 객체를 생성해서 반환합니다. 만약 하나라도 실패하면 전체 함수가 예외를 throw하게 됩니다.

정리

이 코드를 이해하셨다면 여러분도 이제 coroutineScope 함수를 어떤 경우에 어떻게 사용해야 하는지 질문을 받으셨다면 자신있게 예를 들어가면서 설명하실 수 있으실 겁니다!

coroutineScope는 다음과 같은 상황에서 사용하세요:

  • 중단 함수 내에서 여러 비동기 작업을 동시에 실행해야 할 때
  • 구조적 동시성을 보장하면서 안전한 코루틴 스코프가 필요할 때
  • 외부 스코프의 생명주기에 영향을 주지 않으면서 내부에서 병렬 처리를 하고 싶을 때

이제 여러 개의 API를 동시에 호출해야 하는 상황에서 coroutineScope를 자신있게 사용할 수 있을 것입니다. 다음 글에서는 예외 처리가 다른 supervisorScope에 대해 알아보겠습니다.

반응형
Comments