앞서 CoroutineDispatcher을 만드는 방법을 알아봤다. 이번에는 코루틴을 만들어 CoroutineDispatcher에 실행 요청하는 방법을 알아보자.
CoroutineDispatcher에 코루틴 실행 요청하기
CoroutineDispatcher에 코루틴을 실행 요청하는 작업은 대표적으로 launch와 aync 두 가지 메서드를 통해 가능하다. 결과 반환이 없는 단순 작업에는 launch를 결과 반환이 필요한 작업에는 async를 사용한다.
결과 반환 | 반환타입 | |
launch | X | Job |
async | O | Defered<T> |
결과를 반환하지 않는 launch
launch는 결과를 반환하지 않고 launch 수행 시 job이 반환된다.
val job: Job = launch { println(1) }
결과를 반환하는 async
async는 결과를 반환하며 결과값은 Deferred로 감싸서 반환된다. Deferred는 미래에 올 수 있는 값을 담아놓을 수 있는 객체이다.
아래 예제에는 async 블록의 마지막 줄에 있는 1이 반환되어야 하므로 Deferred<Int>값이 반환되었다.
fun main() = runBlocking<Unit> {
val deferredInt: Deferred<Int> = async {
1 // 마지막 줄 반환
}
val value = deferredInt.await()
println(value) // 1 출력
}
Deferred<T>의 await()메서드가 수행되면 await을 호출한 코루틴(위의 코드에서는 runBlocking 코루틴)은 결과가 반환되기까지 스레드를 양보하고 대기한다. 우리는 이를 코루틴이 일시 중단 되었다고 한다. 이러한 특성으로 인해 await() 메서드는 일시 중단이 가능한 코루틴 내부에서만 사용이 가능하다. 만약 일반 함수에서 await을 사용하면 일시 중단 함수는 suspend fun(일시중단 함수)에서만 호출될 수 있다는 오류가 난다.
이후 결과가 반환되었을 때, 코루틴은 다시 재개되고, 위의 예제의 println가 수행되어 1이 출력된다.
위에서 두가지 방식을 통해 디스패처에 코루틴을 실행 요청해봤다. 아래에서는 서로 다른 Dispatcher에 코루틴을 실행하도록 요청하는 방법에 대해 알아볼 것이다.
서로 다른 Dispatcher에 Coroutine 실행 요청하기
데이터베이스로부터 Array<Int>를 받아와서 정렬한다음 텍스트뷰에 출력하는 과정을 한다고 생각해보자. 이 과정에는 파일 입출력(Dispatchers.IO), Array 정렬(Dispatchers.Default), 텍스트 뷰 출력(메인 스레드 디스패처) 과 같이 여러 Dispatcher에 맞는 작업이 들어가는데 코루틴은 다양한 작업을 서로 다른 Dispatcher에 요청하기 위한 간편한 방법을 제공한다. 아래에서 살펴보도록 하자.
코드는 아래와 같아진다.
suspend fun updateUI() = coroutineScope {
// 1. 데이터베이스 입출력 작업을 해야 하므로 IO Dispatcher을 사용해 새로운 코루틴 실행
val deferredInt: Deferred<Array<Int>> = async(Dispatchers.IO) {
delay(1000L) // 데이터베이스로부터 데이터 가져오는 시간
arrayOf(3, 1, 2, 4, 5) // 마지막 줄 반환
}
val value = deferredInt.await()
// 2. Sort해야 하므로 CPU작업을 많이 해야하는 Default Dispatcher을 사용해 새로운 코루틴 실행
val sortedDeferred = async(Dispatchers.Default) {
value.sortedBy { it }
}
val sortedArray = sortedDeferred.await()
// 3. UI를 업데이트 하는 코루틴은 Main Dispatcher에 실행 요청한다.
val updateUIJob = launch(Dispatchers.Main) {
setTextView(sortedArray)
}
}
1. 데이터베이스로부터 [3, 1, 2, 4, 5] Array를 가져오는 작업은 입출력 작업으로 결과를 반환 받아야 하는 작업이다. 따라서 async를 사용해 새로운 코루틴을 실행하며, 작업의 종류는 입출력(I/O) 작업이므로 입출력 전용 디스패처인 Dispatchers.IO 를 사용한다.
아래와 같이 async뒤에 Dispatchers.IO 를 붙임으로써 새로운 코루틴이 IO 스레드에서 실행되도록 만들 수 있다.
// 1. 데이터베이스 입출력 작업을 해야 하므로 IO Dispatcher을 사용해 새로운 코루틴 실행
val deferredInt: Deferred<Array<Int>> = async(Dispatchers.IO) {
delay(1000L) // 데이터베이스로부터 데이터 가져오는 시간
arrayOf(3, 1, 2, 4, 5) // 마지막 줄 반환
}
2. 이제 정렬을 해야한다. 정렬은 CPU 바운드 작업이므로 Dispatchers.Default에서 수행돼야 한다. 따라서 결과를 반환받을 수 있도록 async를 사용하며 Dispatcher은 Default로 세팅한다. 이때 내부에서 파일 입출력이 끝나기 까지 수행을 일시정지하기 위해 deferredInt.await() 을 사용한다.
// 2. Sort해야 하므로 CPU작업을 많이 해야하는 Default Dispatcher을 사용해 새로운 코루틴 실행
val sortedDeferred = async(Dispatchers.Default) {
value.sortedBy { it }
}
3. 마지막 작업은 텍스트뷰에 결과값을 세팅하는 작업이다. 이 작업은 UI작업이므로 Dispatchers.Main에서 수행돼야 한다. 따라서 launch(Dispatchers.Main)을 통해 메인 스레드에 새로 생성된 코루틴을 할당한다.
// 3. UI를 업데이트 하는 코루틴은 Main Dispatcher에 실행 요청한다.
val updateUIJob = launch(Dispatchers.Main) {
setTextView(sortedArray)
}
정리
위에서 Dispatcher에 코루틴을 붙이는 다양한 방법을 살펴보았다. 이를 정리하면 다음과 같다.
- 결과 반환이 필요 없을 때는 launch를 필요할 때는 async를 사용한다.
- 코루틴을 생성할 때 Dispatcher을 설정하는 것만으로 서로 다른 Dispatcher에 새로운 코루틴의 실행을 요청할 수 있다.
Kotlin Coroutines 공식 기술 문서 번역이 GitHub 오픈소스로 배포되었습니다. Starganizer가 되어 오픈소스를 지지해주세요.