코루틴 (Coroutine)
코루틴은 '동시'를 뜻하는 co와 '작업들의 집합'을 뜻하는 routine이 합쳐져 만들어진 단어입니다.
일반적으로 루틴은 단 하나의 입구점과 출구점을 가지는 반면, 코루틴은 여러 개의 입구점과 출구점을 가질 수 있습니다.
따라서, 코루틴은 이전에 실행이 중단된 지점에서 다시 실행을 재개할 수 있는 기능을 가집니다.
코루틴을 적용하면 데이터베이스 또는 네트워크 작업 같은 장시간 작업으로 인한 메인 스레드 블로킹 현상을 줄일 수 있으며, 비동기 작업 중 예외 발생에 따른 메모리 누수를 방지할 수 있습니다.
즉, 비동기적으로 실행되는 코드를 간소화하기 위해 동시성 프로그래밍 개념을 코틀린에 도입한 것을 코루틴이라고 합니다.
코루틴 의존성 추가
코루틴을 사용하기 위해서 build.gradle에 Coroutine 라이브러리를 추가합니다.
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
코루틴 컨텍스트 (Coroutine Context)
코루틴 컨텍스트는 key와 element를 갖는 map을 의미합니다.
여기서 element에는 코루틴 컨텍스트의 서브타입으로 Dispatcher, Job, Deferred 등이 들어갑니다.
코루틴 컨텍스트를 이용하면 코루틴 취소와 같은 작업을 간단히 처리할 수 있습니다.
- Dispatcher : 코루틴을 처리할 스레드를 설정하고 할당하는 역할
- Job : 생성된 코루틴을 제어하는 역할 (생명 주기, 부모 자식 관계 정리 및 관리)
Coroutine Dispatcher
Coroutine Dispatcher는 코루틴을 스레드에 배분하는 역할을 담당합니다.
스레드 풀에서 스레드를 하나 할당해 코루틴을 배당합니다.
즉, Coroutine Dispatcher는 코루틴을 적당한 스레드에 할당하며, 코루틴 실행 도중 일시 정지 또는 실행 재개를 담당합니다.
// Thread Pool에서 Dispatcher 생성
val threadPoolDispatcher = newFixedThreadPoolContext(5, "Thread 1")
// 싱글 Thread에서 Dispatcher 생성
val singleThreadDispatcher = newSingleThreadContext("Thread 2")
- Dispatchers.Default : 데이터 정렬, 복잡한 연산 등과 같은 CPU 사용량이 많은 작업에 사용
- 안드로이드 기본 스레드풀을 사용하여 코루틴 실행
- Dispatchers.Main : UI와 상호작용하기 위해서만 사용
- 안드로이드 기본 스레드에서 코루틴 실행
- Dispatchers.IO : File I/O, Network I/O, Parser 작업 등과 같은 입출력 작업에 사용
- Dispatchers.Unconfined : 특수한 상황에서 코루틴 실행 (사용을 권장하지 않음)
- 호출한 context를 기본으로 사용하는데 중단 후 다시 실행될 때 context가 바뀌면 바뀐 context를 따라감
Coroutine Job
Coroutine Job은 코루틴의 수명 주기를 제어하는 역할을 담당합니다.
launch 또는 async로 만드는 각 코루틴은 코루틴을 고유하게 식별하고 수명 주기를 관리하는 Job 인스턴스를 반환합니다.
- start() : 현재 Job과 관련된 코루틴 시작 (동작 중 = true, 준비/종료 = false)
- join() : 현재 Job이 완료될 때까지 코루틴 일시 중단 (async Deferred의 await 와 같은 역할)
- cancel() : 현재 Job을 즉시 종료 (Thread의 interrupt 와 같은 역할)
- cancelAndJoin() : 현재 Job을 즉시 종료하고 종료된 Job이 완료될 때까지 호출 코루틴 일시 중단
- cancelChildren() : 현재 Coroutine Scope 내에 작성한 자식 코루틴들 모두 종료 (부모 코루틴은 종료되지 않음)
val job = Job()
CoroutineScope(Dispatchers.Default+job).launch {
// Child O
launch {
println("Coroutine1 Start")
delay(1000)
println("Coroutine1 End")
}
// Child X -> 새로운 Coroutine Scope를 생성하여 코루틴 실행
CoroutineScope(Dispatchers.IO).launch {
println("Coroutine2 Start")
delay(1000)
println("Coroutine2 End")
}
// Child O -> (Coroutine Context + job)을 통해 상위 코루틴과 연결
CoroutineScope(Dispatchers.IO + job).launch {
println("Coroutine3 Start")
delay(1000)
println("Coroutine3 End")
}
}
delay(500)
job.cancel()
delay(3000)
println("Finish!")
/*
<결과>
Coroutine1 Start
Coroutine2 Start
Coroutine3 Start
Coroutine2 End
Finish!
*/
코루틴 스코프 (Coroutine Scope)
코루틴 스코프는 코루틴이 실행되는 영역을 의미합니다.
코루틴 스코프에는 GlobalScope와 별도로 지정한 CoroutineScope가 있습니다.
- GlobalScope : 앱이 실행하는 동안 혹은 장시간 실행되어야 하는 코루틴의 경우에 사용
- 앱의 생명주기와 함께 동작하기 때문에 실행 도중에 별도의 생명주기를 관리할 필요가 없습니다.
- CoroutineScope : 필요할 때만 열고 완료되면 닫아주는 코루틴의 경우에 사용
- ViewModelScope : ViewModel 인스턴스에서 사용할 코루틴의 경우에 사용
- ViewModel 인스턴스가 소멸될 때 자동으로 취소됩니다.
// GlobalScope의 경우
GlobalScope.launch(Dispatchers.Main) {
// TO DO
}
// CoroutineScope의 경우
// Dispatcher를 통해 코루틴이 실행될 스레드를 지정할 수 있습니다.
CoroutineScope(Dispatchers.IO).launch {
// TO DO
}
코루틴 빌더 (Coroutine Builder)
코루틴 빌더는 코루틴이 생성하는 메소드를 의미합니다.
코루틴 빌더에는 launch, async, withContext, runBlocking 등이 있습니다.
- launch : 결과값이 없는 코루틴
- 자체/자식 코루틴 실행을 취소할 수 있는 Job 반환
- async : 결과값이 있는 코루틴
- 전역으로 예외 처리 가능
- 결과 또는 예외를 반환 가능한 Deferred 반환
- withContext : 결과값을 그대로 반환하는 코루틴
- runBlocking : 작업이 완료할 때까지 스레드를 점유하는 코루틴
- Blocking 코드를 일시 중지(Suspend) 가능한 코드로 연결하기 위한 용도
- main 함수나 Unit Test 때 많이 사용됨
// launch로 새로운 코루틴을 시작하고 Job 객체 획득
val job: Job = launch {
// TO DO
}
// 코루틴이 종료될 때까지 기다림
runBlocking {
job.join()
}
CoroutineScope(Dispatchers.Main).launch {
// async로 Int 결과값 반환
val deferred1: Deferred<Int> = async(Dispatchers.IO) {
// TO DO
}
// async로 Boolean 결과값 반환
val deferred2: Deferred<Boolean> = async(Dispatchers.Default) {
// TO DO
}
// async 작업이 완료 되고 난 후 호출
val result1 = deferred1.await()
val result2 = deferred2.await()
}
GlobalScope.launch(Dispatchers.IO) {
val v = withContext(Dispatchers.Main) {
var total = 0
for (i in 1..10) {
delay(100)
total += i
}
total
}
print("result: $v")
print("Do something in IO thread")
}
Suspend 키워드
코루틴에서는 장시간 작업에 대해서도 간편한 코드를 작성할 수 있게 해줍니다.
특히, 비동기 콜백 작업을 순차적 코드로 간단하게 작성할 수 있다는 장점이 있습니다.
함수 앞에 suspend 키워드를 붙임으로써 코루틴이 적용되었다는 것을 나타냅니다.
GlobalScope.launch {
val result = doSomething()
print("Done Something, $result")
}
private suspend fun doSomething(): Int {
val value: Int = GlobalScope.async(Dispatchers.IO) {
// TO DO
}.await()
return value
}
'안드로이드 > 개념' 카테고리의 다른 글
[Android] Clean Architecture (0) | 2023.12.19 |
---|---|
[Android] Flow (0) | 2023.12.01 |
[Android] Rx와 Observable (0) | 2023.11.03 |
[Android] 앱 아키텍처 패턴 (MVC, MVP, MVVM, MVI) (0) | 2023.10.27 |
[Android] 정규표현식 (0) | 2023.10.11 |