code

CoroutineContext의 이해

sinply 2025. 3. 11. 23:05
반응형
SMALL

1. CoroutineContext란?

**CoroutineContext**는 코루틴의 실행 환경을 정의하는 컨텍스트 정보를 담고 있는 Key-Value 형식의 데이터 구조입니다.
코루틴을 실행할 때 디스패처(Dispatcher), Job, 예외 핸들러 등의 속성을 설정할 수 있도록 도와줍니다.

💡 한 마디로?

코루틴의 실행 방식과 환경을 정의하는 역할


2. CoroutineContext의 구성 요소

CoroutineContext는 여러 요소들의 조합으로 이루어지며, 각 요소는 특정 역할을 담당합니다.

요소설명

Dispatcher 코루틴이 실행될 스레드를 결정 (예: Dispatchers.IO, Dispatchers.Main)
Job 코루틴의 실행 단위를 나타내며, 취소나 부모-자식 관계를 관리
CoroutineName 코루틴의 이름을 설정하여 디버깅을 쉽게 함
CoroutineExceptionHandler 예외 발생 시 처리할 핸들러 지정

각 요소는 CoroutineContext의 일부로 결합되며, 하나의 CoroutineContext는 여러 요소를 포함할 수 있습니다.


3. CoroutineContext의 원리

  • CoroutineContext는 불변 객체
    • plus(+) 연산을 통해 새로운 CoroutineContext를 만들 수 있음 (기존 값을 수정하는 것이 아니라 새로운 컨텍스트를 생성)
  • 코루틴의 실행 환경을 설정하는 데 사용
    • 어떤 스레드에서 실행할지, 어떤 Job이 포함되는지 등의 실행 규칙을 설정 가능
  • 필요한 컨텍스트만 덮어쓸 수 있음
    • 기존의 컨텍스트에서 일부 요소만 변경하여 새로운 컨텍스트 생성 가능

4. CoroutineContext 사용법 및 예제

(1) 기본적인 CoroutineContext 활용

import kotlinx.coroutines.*

fun main() = runBlocking {
    val context = Dispatchers.Default + CoroutineName("MyCoroutine")
    
    launch(context) {
        println("코루틴 실행 중... (스레드: ${Thread.currentThread().name})")
    }
}

코루틴 실행 중... (스레드: DefaultDispatcher-worker-1)

설명

  • Dispatchers.Default를 사용하여 백그라운드 스레드에서 실행
  • CoroutineName("MyCoroutine")을 추가하여 디버깅 시 이름을 확인 가능

(2) CoroutineContext 요소를 개별적으로 다루기

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = Job()
    val dispatcher = Dispatchers.IO
    val coroutineName = CoroutineName("CustomCoroutine")

    val context = job + dispatcher + coroutineName

    launch(context) {
        println("Coroutine 실행! (스레드: ${Thread.currentThread().name})")
    }
}

설명

  • Job()을 추가하여 코루틴을 개별적으로 취소할 수 있도록 설정
  • Dispatchers.IO를 사용하여 입출력(IO) 작업을 위한 스레드에서 실행

(3) CoroutineExceptionHandler를 추가하여 예외 처리

 
import kotlinx.coroutines.*

fun main() = runBlocking {
    val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
        println("예외 발생: ${throwable.message}")
    }

    val context = Dispatchers.Default + exceptionHandler

    launch(context) {
        throw RuntimeException("에러 발생!")
    }
}

예외 발생: 에러 발생!

설명

  • CoroutineExceptionHandler를 사용하여 코루틴 내에서 발생한 예외를 처리
  • 예외가 발생해도 앱이 강제 종료되지 않고 정상적으로 예외를 로그로 출력

(4) 코루틴 내부에서 Context 변경하기

import kotlinx.coroutines.*

fun main() = runBlocking {
    val context = Dispatchers.IO + CoroutineName("InitialContext")

    launch(context) {
        println("초기 컨텍스트: ${coroutineContext}")

        withContext(Dispatchers.Default + CoroutineName("ChangedContext")) {
            println("변경된 컨텍스트: ${coroutineContext}")
        }
    }
}

초기 컨텍스트: [CoroutineName(InitialContext), Dispatchers.IO]
변경된 컨텍스트: [CoroutineName(ChangedContext), Dispatchers.Default]

설명

  • withContext를 사용하면 코루틴 내부에서 특정 블록만 다른 컨텍스트에서 실행 가능
  • Dispatchers.IO → Dispatchers.Default로 변경되었음을 확인 가능

5. CoroutineContext를 활용한 베스트 프랙티스

🔹 컨텍스트를 조합하여 원하는 실행 환경을 구성하자

  • Dispatchers.IO + CoroutineExceptionHandler + Job 등을 조합하여 효율적인 코루틴 실행 환경 설정

🔹 예외 처리는 반드시 CoroutineExceptionHandler를 활용

  • 예외가 발생해도 앱이 정상적으로 동작하도록 핸들링 필수

🔹 withContext를 활용하여 필요한 경우에만 컨텍스트 변경

  • 전체 코루틴이 아닌 특정 블록만 다른 컨텍스트에서 실행할 때 사용

🔹 코루틴의 Job을 활용하여 명확한 취소 관리

  • val job = Job()을 사용하여 부모-자식 관계를 유지하고 적절히 취소

6. 정리

CoroutineContext란?

  • 코루틴의 실행 환경을 정의하는 Key-Value 형식의 데이터 구조

CoroutineContext의 주요 요소

  • Dispatcher (스레드 결정), Job (코루틴 관리), CoroutineExceptionHandler (예외 처리), CoroutineName (디버깅 용도)

핵심 개념

  • 불변 객체이며, + 연산으로 새로운 컨텍스트를 만들 수 있음
  • withContext를 사용하여 특정 블록에서만 컨텍스트 변경 가능
  • CoroutineExceptionHandler로 예외를 안전하게 처리

LIST