코루틴 스코프와 컨텍스트 - swkim0128/PARA GitHub Wiki
코루틴 스코프는 코루틴이 실행될 범위(생명 주기)를 관리하는 역할을 한다. 즉, 코루틴이 시작되고, 언제 종료될지를 관리하는 컨테이너 역할을 하며, 코루틴의 구조적 동시성을 보장한다.
코루틴 스코프를 사용하면 코루틴이 특정 범위 내에서 실행되도록 보장하고, 예외 발생 시 자동으로 취소할 수 있다.
- 앱이 종료될 때까지 코루틴이 살아있음 (전역 코루틴).
- 구조적 동시성이 보장되지 않음 → 추천되지 않음.
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch {
delay(1000L)
println("GlobalScope 실행!")
}
println("메인 함수 종료") // 코루틴이 실행될 수도, 안 될 수도 있음
Thread.sleep(2000L) // 프로그램이 종료되지 않도록 유지
}
- 메인 스레드를 블로킹하며 실행됨.
- 주로 테스트 및 main 함수에서 코루틴을 실행할 때 사용.
import kotlinx.coroutines.*
fun main() = runBlocking {
println("runBlocking 시작")
launch {
delay(1000L)
println("launch 실행 완료!")
}
println("runBlocking 종료") // launch 완료 전에 실행됨
}
출력 결과:
runBlocking 시작
runBlocking 종료
(1초 후)
launch 실행 완료!
- 수명이 제한된 코루틴을 생성.
- CoroutineScope.launch {} 형태로 사용하며, 특정 스코프 내에서 실행됨.
- 코루틴을 취소하면 해당 스코프 내의 모든 코루틴이 자동으로 취소됨.
import kotlinx.coroutines.*
fun main() = runBlocking {
val scope = CoroutineScope(Dispatchers.Default)
scope.launch {
delay(1000L)
println("CoroutineScope 실행!")
}
delay(500L)
scope.cancel() // 스코프 내의 모든 코루틴 취소
println("코루틴 스코프 취소됨")
}
출력 결과:
코루틴 스코프 취소됨
(코루틴 실행되지 않음)
✅ scope.cancel()을 호출하면 스코프 내의 모든 코루틴이 취소됨.
코루틴 컨텍스트는 코루틴이 실행되는 환경을 정의하는 요소들의 집합이다. 컨텍스트에는 여러 요소가 포함되며, 대표적인 요소는 아래와 같다.
요소 | 설명 |
---|---|
Job | 코루틴의 실행 상태를 관리 |
Dispatcher | 코루틴이 실행될 스레드를 지정 |
CoroutineName | 디버깅을 위한 코루틴 이름 설정 |
ExceptionHandler | 예외 처리 핸들러 |
Dispatcher는 코루틴이 실행될 스레드를 지정하는 역할을 한다. 코루틴은 기본적으로 하나의 스레드에서 실행되지만, Dispatcher를 사용하면 특정 스레드에서 실행할 수 있다.
주요 Dispatcher 종류
Dispatcher | 설명 |
---|---|
Dispatchers.Default | 기본 디스패처 (CPU 연산 작업) |
Dispatchers.IO | I/O 작업(파일, 네트워크) |
Dispatchers.Main | UI 스레드(Android 전용) |
Dispatchers.Unconfined | 호출한 스레드에서 실행됨 |
Dispatcher 예제
import kotlinx.coroutines.*
fun main() = runBlocking {
launch(Dispatchers.Default) {
println("Default 실행: ${Thread.currentThread().name}")
}
launch(Dispatchers.IO) {
println("IO 실행: ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
println("Unconfined 실행: ${Thread.currentThread().name}")
}
}
출력 예제:
Default 실행: DefaultDispatcher-worker-1
IO 실행: DefaultDispatcher-worker-2
Unconfined 실행: main
✅ Dispatchers.Default와 Dispatchers.IO는 백그라운드 스레드에서 실행되지만, ✅ Dispatchers.Unconfined는 현재 실행 중인 스레드(main)에서 실행됨.
Job 객체를 사용하면 코루틴의 상태를 제어할 수 있다.
Job 예제
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch {
repeat(5) { i ->
println("작업 실행 중... $i")
delay(500L)
}
}
delay(1500L)
job.cancel() // 코루틴 취소
println("작업 취소됨")
}
출력 결과:
작업 실행 중... 0
작업 실행 중... 1
작업 실행 중... 2
작업 취소됨
✅ job.cancel()을 호출하면 해당 코루틴이 취소됨.
Job과 달리, SupervisorJob은 하위 코루틴에서 예외가 발생해도 다른 코루틴에 영향을 주지 않는다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val supervisor = CoroutineScope(SupervisorJob())
supervisor.launch {
delay(500L)
throw Exception("첫 번째 코루틴 예외 발생!")
}
supervisor.launch {
delay(1000L)
println("두 번째 코루틴 실행 성공!")
}
delay(1500L)
println("메인 종료")
}
출력 결과:
(0.5초 후) 예외 발생
(1초 후) 두 번째 코루틴 실행 성공!
(1.5초 후) 메인 종료
✅ SupervisorJob을 사용하면 하위 코루틴이 독립적으로 실행될 수 있음.
코루틴 내에서 발생하는 예외를 처리하려면 CoroutineExceptionHandler를 사용할 수 있다.
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
fun main() = runBlocking {
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("예외 처리됨: ${exception.message}")
}
val scope = CoroutineScope(SupervisorJob() + exceptionHandler)
scope.launch {
throw Exception("코루틴 오류 발생!")
}
delay(1000L)
println("메인 종료")
}
출력 결과:
예외 처리됨: 코루틴 오류 발생!
메인 종료
✅ CoroutineExceptionHandler를 사용하면 예외가 발생해도 프로그램이 중단되지 않고 처리할 수 있음.
- GlobalScope → 전역적으로 실행되며 구조적 동시성을 보장하지 않음.
- runBlocking → 메인 스레드를 블로킹하며 코루틴을 실행함.
- CoroutineScope → 특정 범위에서 실행되며 종료 시 코루틴을 자동 취소.
- Dispatcher → 실행 스레드 지정 (Default, IO, Main, Unconfined)
- Job → 실행 상태 관리 (cancel()로 종료 가능)
- SupervisorJob → 독립적인 코루틴 실행 가능
- CoroutineExceptionHandler → 예외를 안전하게 처리
코루틴 스코프와 컨텍스트를 활용하면 안정적이고 효율적인 비동기 프로그래밍을 구현할 수 있다.