업무 기록/ETC

Kotlin(코틀린) 코루틴으로 비동기 프로그래밍

code2772 2024. 11. 20. 02:33
728x90
반응형

Kotlin의 강력한 기능 중 하나인 코루틴(Coroutines)에 대해 자세히 알아보도록 하겠습니다. 비동기 프로그래밍을 쉽고 효율적으로 구현할 수 있는 코루틴의 기본 개념부터 실전 활용법까지 살펴보겠습니다.

1. 코루틴이 뭔가요? 🤔

코루틴은 비동기 프로그래밍을 위한 코틀린의 솔루션입니다. 기존의 콜백이나 Future/Promise 패턴의 복잡성을 줄이고, 동시성 프로그래밍을 더욱 직관적으로 작성할 수 있게 해줍니다.

실생활 비유로 이해하기

식당에서 일하는 웨이터를 생각해봅시다. 웨이터가 한 테이블의 주문을 받고 주방에 전달한 후, 음식이 나올 때까지 그 자리에서 기다린다면 어떨까요? 매우 비효율적이겠죠! 실제로는 주문을 전달한 후 다른 테이블의 손님도 응대합니다.

코루틴도 이와 같습니다! 시간이 오래 걸리는 작업(예: 네트워크 요청, 파일 다운로드)을 기다리는 동안 다른 작업을 할 수 있게 해주는 똑똑한 도구입니다.

 

왜 코루틴을 사용하나요?

  1. 간단해요: 복잡한 비동기 코드를 쉽게 작성할 수 있어요
  2. 효율적이에요: 수많은 작업을 동시에 처리할 수 있어요
  3. 안전해요: 오류 처리가 쉽고 안전해요

 

2. 코루틴 시작하기 🚀

기본 설정

먼저 코루틴을 사용하기 위한 준비를 해봅시다:

 

1. 기본 코루틴 설정 👣

// build.gradle.kts
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}

📝 설명

  • kotlinx-coroutines-core: 코루틴의 핵심 라이브러리
  • 1.7.3: 안정적인 최신 버전
  • 이 의존성 추가로 코루틴의 모든 기능 사용 가능

 

2. 첫 번째 코루틴 예제 🚀

fun main() = runBlocking {
    println("시작")

    launch {
        delay(1000)
        println("코루틴 내부")
    }

    println("메인 코드 실행 중")
}

📝 상세 설명

  • runBlocking: 코루틴 스코프를 생성하고 완료될 때까지 현재 스레드를 블록
  • launch: 새로운 코루틴을 시작하는 빌더. 별도의 작업을 비동기적으로 실행
  • delay(1000): 1초 동안 현재 코루틴을 일시 중단 (다른 코루틴이 실행될 수 있음)
  • 실행 순서: "시작" → "메인 코드 실행 중" → (1초 후) → "코루틴 내부"

 

3. 스코프(Scope) 📦

스코프는 코루틴의 생명주기를 관리하는 범위입니다.

주요 스코프 종류

  1. GlobalScope
    GlobalScope.launch {
    // 앱 전체 수명주기와 함께 동작
    }
    • 앱의 시작부터 종료까지 유지
    • 메모리 누수 위험이 있어 사용 주의
  2. CoroutineScope
    val scope = CoroutineScope(Dispatchers.Default)
    scope.launch {
    // 특정 목적을 위한 스코프
    }
    • 명시적인 생명주기 관리
    • 필요할 때 취소 가능
  3. viewModelScope (안드로이드)  
class MyViewModel : ViewModel() {
    init {
        viewModelScope.launch {
// ViewModel의 생명주기와 함께 동작
        }
    }
}

- ViewModel 인스턴스의 생명주기와 연동

 

4. 컨텍스트(Context) 🎯

코루틴이 실행되는 환경을 정의합니다.

주요 컨텍스트 요소

  1. Dispatchers (디스패처) 종류:
    withContext(Dispatchers.IO) {
    // I/O 작업 실행
    }
     
    • Dispatchers.Main: UI 작업용
    • Dispatchers.IO: 파일/네트워크 작업용
    • Dispatchers.Default: 복잡한 연산용
  2. Job
    • 코루틴의 생명주기 관리
    • 취소 가능한 작업 단위
kotlin
Copy
val job = launch {
// 작업 내용
}
job.cancel()// 작업 취소

 

 

5. 코루틴 빌더 🏗️

코루틴을 시작하는 다양한 방법을 제공합니다.

주요 빌더

  1. launch
    val job = launch {
    // 결과를 반환하지 않는 코루틴
    }
    • Fire-and-forget 방식
    • Job 객체 반환
  2. async
    val deferred = async {
    // 결과를 반환하는 코루틴
        "결과"
    }
    val result = deferred.await()// 결과 받기
     
    • 결과를 반환하는 코루틴
    • Deferred<T> 객체 반환
  3. runBlocking
    runBlocking {
    // 현재 스레드를 블록하는 코루틴
    }
    • 주로 테스트에서 사용
    • 메인 함수에서 사용

 

6. 실제 사용 예시 💡

네트워크 요청 처리

class UserRepository {
    suspend fun fetchUser(userId: String) = withContext(Dispatchers.IO) {
// 네트워크 호출
        api.getUser(userId)
    }
}

class UserViewModel(private val repository: UserRepository) {
    init {
        viewModelScope.launch {
            try {
                val user = repository.fetchUser("123")
// UI 업데이트
            } catch (e: Exception) {
// 에러 처리
            }
        }
    }
}

병렬 처리

kotlin
Copy
suspend fun loadData() = coroutineScope {
    val data1 = async { fetchData1() }
    val data2 = async { fetchData2() }

// 두 데이터 모두 로드될 때까지 대기
    val combinedData = combineData(data1.await(), data2.await())
}

 

7. 코루틴 장점 정리 ✨

  1. 간단한 비동기 처리
    • 동기 코드처럼 작성 가능
    • 가독성 향상
    • 유지보수 용이
  2. 효율적인 리소스 사용
    • 경량 스레드 사용
    • 시스템 리소스 절약
    • 많은 동시 작업 가능
  3. 구조화된 동시성
    • 생명주기 관리 용이
    • 메모리 누수 방지
    • 에러 처리 간편

 

8. 주의사항 ⚠️

  1. GlobalScope 사용 주의
    • 가능하면 제한된 스코프 사용
    • 명시적인 생명주기 관리 필요
  2. 예외 처리 필수
  3. 컨텍스트 적절한 사용
    • UI 작업은 반드시 Main 디스패처
    • 무거운 작업은 IO 또는 Default 디스패처
반응형