BE/Kotlin

[Kotlin] 코루틴 (Coroutines)

baek-dev 2025. 3. 2. 14:03

⚡ 1. 코루틴(Coroutines)란?

코루틴(Coroutine) 은 Kotlin에서 제공하는 비동기 프로그래밍을 위한 경량 쓰레드 개념입니다.

기존의 ThreadRxJava보다 가볍고 효율적으로 비동기 작업을 관리할 수 있도록 설계되었습니다.

 

✅ 코루틴의 주요 특징

 

🔹 경량성 → 수천 개의 코루틴을 실행해도 적은 리소스를 사용

🔹 비동기 & 논블로킹 → 메인 쓰레드를 차단하지 않고 동작

🔹 구조적 동시성(Structured Concurrency)CoroutineScope를 활용해 일괄적으로 관리 가능

🔹 기존 코드와의 쉬운 통합 → 기존 Thread, RxJava보다 간결한 코드 작성 가능

 

⚡ 2. 코루틴의 기본 개념

🏗 코루틴의 주요 구성 요소

개념 설명
CoroutineScope 코루틴이 실행되는 범위를 정의 (예: GlobalScope, MainScope, viewModelScope)
launch 새로운 코루틴을 실행 (결과값 없음, Job 반환)
async 값을 반환하는 코루틴 실행 (Deferred<T> 반환, await()로 값 얻기)
suspend 일시 중단이 가능한 함수 (delay(), withContext(), await() 같은 함수에서 사용)
withContext 특정 쓰레드 컨텍스트에서 코드 실행 (Dispatchers.IO, Dispatchers.Main 등)
Job 코루틴의 실행 단위를 나타내며, cancel()을 호출하여 중단 가능
Deferred async가 반환하는 결과값을 나타내며, await()으로 값을 가져올 수 있음

 

⚡ 3. 코루틴 기본 예제

✅ 1) launch로 코루틴 실행

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        delay(1000L)
        println("코루틴 실행 완료!")
    }
    println("메인 실행")
}

🖥️ 출력 결과

메인 실행
(1초 후)
코루틴 실행 완료!

🔹 launch는 비동기로 실행되며, delay(1000L) 동안 대기 후 "코루틴 실행 완료!"를 출력

 

✅ 2) async로 결과값 반환

import kotlinx.coroutines.*

fun main() = runBlocking {
    val result = async {
        delay(1000L)
        "Hello, Coroutines!"
    }
    println("메인 실행")
    println(result.await()) // await()을 호출해야 결과를 받을 수 있음
}

🖥️ 출력 결과

메인 실행
(1초 후)
Hello, Coroutines!

🔹 async는 값을 반환하는 코루틴을 실행하며, await()을 호출해야 결과를 받을 수 있음.

 

⚡ 4. 코루틴의 실행 컨텍스트 (Dispatchers)

Kotlin 코루틴에서는 작업을 실행할 쓰레드를 지정하는 Dispatchers 를 사용할 수 있습니다.

Dispatcher 설명
Dispatchers.Main UI 작업 수행 (Android에서 주로 사용)
Dispatchers.IO I/O 작업 수행 (파일 읽기, 네트워크 요청 등)
Dispatchers.Default CPU 연산이 집중된 작업 수행 (데이터 처리, 대규모 계산)
Dispatchers.Unconfined 처음 호출된 쓰레드에서 실행되지만, 이후 상황에 따라 변경 가능
newSingleThreadContext("Thread") 새로운 단일 쓰레드를 생성하여 실행

 

✅ 1) Dispatchers.IO에서 네트워크 작업 수행

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch(Dispatchers.IO) { // IO 작업을 위한 컨텍스트 지정
        val result = fetchData()
        println(result)
    }
}

suspend fun fetchData(): String {
    delay(1000L)
    return "데이터 가져오기 완료!"
}

🔹 Dispatchers.IO는 네트워크 요청 또는 파일 I/O 작업을 수행할 때 적합.

🔹 fetchData()suspend 함수이므로 코루틴 안에서만 호출 가능.

 

✅ 2) withContext()로 컨텍스트 변경

import kotlinx.coroutines.*

fun main() = runBlocking {
    val result = withContext(Dispatchers.IO) { fetchData() }
    println(result)
}

🔹 withContext(Dispatchers.IO)를 사용하면 특정 코드 블록만 IO 컨텍스트에서 실행 가능.

🔹 async(Dispatchers.IO)와 달리 withContext는 반환 값을 직접 받을 수 있음.

 

⚡ 5. 코루틴 예외 처리 (try-catch & CoroutineExceptionHandler)

✅ 1) try-catch로 예외 처리

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        try {
            riskyTask()
        } catch (e: Exception) {
            println("에러 발생: ${e.message}")
        }
    }
}

suspend fun riskyTask() {
    delay(1000L)
    throw IllegalStateException("예상치 못한 오류 발생!")
}

🖥️ 출력 결과

에러 발생: 예상치 못한 오류 발생!

🔹 try-catch를 사용하여 코루틴 내부에서 발생한 예외를 처리 가능.

 

✅ 2) CoroutineExceptionHandler로 예외 처리

import kotlinx.coroutines.*

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

    val job = GlobalScope.launch(handler) {
        throw IllegalArgumentException("잘못된 입력값!")
    }

    job.join()
}

🖥️ 출력 결과

예외 발생: 잘못된 입력값!

🔹 CoroutineExceptionHandlerlaunch에서 발생한 예외를 글로벌하게 처리 가능.

 

⚡ 6. 코루틴의 구조적 동시성 (SupervisorJob & scope 활용)

✅ 1) SupervisorJob을 활용한 독립적인 오류 처리

import kotlinx.coroutines.*

fun main() = runBlocking {
    val supervisor = SupervisorJob()
    val scope = CoroutineScope(Dispatchers.Default + supervisor)

    scope.launch {
        throw IllegalStateException("작업 실패!")
    }

    scope.launch {
        delay(1000L)
        println("다른 작업 정상 실행")
    }

    delay(2000L)
}

🖥️ 출력 결과

다른 작업 정상 실행

🔹 SupervisorJob을 사용하면 하나의 코루틴이 실패해도 다른 코루틴은 영향을 받지 않음.

 

⚡ 7. Flow를 활용한 비동기 데이터 스트림

✅ 1) Flow 기본 예제

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    flowExample().collect { value ->
        println("받은 값: $value")
    }
}

fun flowExample(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(500L)
        emit(i) // 값 방출
    }
}

🖥️ 출력 결과

받은 값: 1
받은 값: 2
받은 값: 3
받은 값: 4
받은 값: 5

🔹 Flow비동기 데이터 스트림을 처리하는 방법으로, emit()을 사용해 값을 순차적으로 방출 가능.

🔹 collect()를 사용하여 Flow 데이터를 수집함.

 

⚡ 결론

코루틴은 가벼운 비동기 작업을 효율적으로 관리할 수 있도록 도와줌.

launch는 단순 실행, async는 결과 반환 (await() 필요).

Dispatchers를 활용해 원하는 쓰레드에서 실행 가능 (Main, IO, Default).

예외 처리는 try-catch 또는 CoroutineExceptionHandler로 가능.

Flow를 사용하면 비동기 데이터 스트림을 쉽게 처리 가능.

 

🚀 코루틴을 활용하면 Kotlin에서 효율적이고 안전한 비동기 처리를 구현할 수 있음!

 

 

 

 

출처 : ChatGPT