공식문서
https://developer.android.com/kotlin/coroutines/coroutines-adv?hl=ko
코루틴은 코틀린에서 제공하는 기능으로, 주로 동시에 여러 작업을 쉽게 처리할 수 있게 해준다. 일반적으로 앱은 여러 작업을 한 번에 처리하는데, 코루틴을 사용하면 비동기 작업을 마치 동기 작업처럼 단순하게 작성할 수 있다.
좀 더 이해하기 쉽게 실생활에서 예를 들어보자.
레스토랑을 떠올리면 된다. 웨이터는 각 테이블에서 음식을 주문받아 주방에 전달한 후 음식이 나올 때까지 기다릴 필요가 없다.
기다리는 대신, 다른 테이블의 주문을 받거나 서빙을 계속할 수 있다. 만약 음식이 나올 때까지 기다리기만 한다면 다른 고객은 주문도 못 하고 불편을 겪게 될 것이다.
마찬가지로 앱에서도 데이터가 네트워크에서 로드될 때까지 화면을 멈추게 하지 않고, 코루틴을 통해 데이터를 로드하는 동안 다른 기능을 계속 사용할 수 있게 할 수 있다.
코루틴을 활용하면 네트워크 호출과 파일 읽기/쓰기 같은 작업도 메인 화면과 따로 진행되도록 만들 수 있다. 덕분에 사용자는 앱이 응답하지 않거나 멈추는 상황을 경험하지 않게 되고, 빠르고 매끄럽게 앱을 사용할 수 있다. 화면이 멈춘다? 한국인은 참지않는다
결론적으로, 코루틴을 사용하면 메인 화면이 멈추지 않고 이런 작업을 백그라운드에서 처리할 수 있어 앱의 반응 속도와 사용성이 크게 개선된다.
코루틴의 핵심 키워드 중 하나가 바로 suspend 이다. suspend 키워드를 통해 일시 중단할 수 있는 함수를 정의할 수 있으며, 해당 함수는 실행 도중 일시적으로 멈추거나 다른 작업을 수행한 뒤 다시 이어서 실행될 수 있다.
이를 통해 애플리케이션이 특정 작업에 블로킹되지 않고도 다양한 작업을 동시에 수행할 수 있게 된다.
예를 들어, 네트워크 요청을 처리하는 함수를 suspend 함수로 정의하면, 요청을 기다리는 동안 다른 작업을 진행할 수 있다. 이로 인해 앱의 응답성이 크게 향상될 수 있다.
아래 예시에서 delay 함수는 코루틴에서 지원하는 함수로, 지정된 시간만큼 비동기적으로 대기할 수 있다. 보통 네트워크 요청에 소요되는 때문에 이렇게 딜레이를 둔다고 한다.
suspend fun fetchData(): String {
delay(1000) // 네트워크 요청을 가정하여 1초 대기
return "Data fetched"
}
안드로이드에서 코루틴을 사용할 때 중요한 구성 요소에는 Job, CoroutineScope, Dispatcher가 있다.
Job은 코루틴 내에서 취소 가능한 작업 단위를 나타낸다. 예를 들어 launch()로 시작된 코루틴은 모두 Job 객체를 반환하며, 이 Job을 통해 작업의 취소, 상태 확인 등이 가능하다.
CoroutineScope는 코루틴의 범위를 정의하는데 사용된다. launch()나 async()와 같은 코루틴 빌더는 항상 특정 스코프 내에서 실행되어야 한다. 이렇게 정의된 범위 안에서 생성된 코루틴은 스코프가 종료될 때 함께 취소될 수 있으므로 메모리 관리와 관련된 문제를 줄이는 데에도 도움을 준다.
안드로이드에서는 일반적으로 MainActivity나 ViewModel 등 클래스에 스코프를 지정하여 해당 범위 내에서 안전하게 코루틴을 사용할 수 있다.
Dispatcher는 코루틴이 실행될 스레드를 결정한다. 코틀린에서는 용도에 따라 다양한 디스패처를 제공하며, 필요에 따라 적절히 선택해서 사용할 수 있다:
안드로이드에서 코루틴을 시작하는 방법으로는 launch, async 이 두 가지가 대표적이다.
새 코루틴을 시작하지만 호출자에게 결과를 반환하지 않는 형태이다. 특정 작업의 완료 여부와 관계없이 '실행 후 자동 삭제'로 간주되는 모든 작업에 적합하다. 예를 들어, 단순히 화면에 메시지를 표시하는 작업 등 결과를 받을 필요가 없는 작업에 적합하다.
launch와 비슷하게 새 코루틴을 시작하지만, 호출자에게 결과를 반환하는 형태이다. 비동기 결과가 필요한 경우 사용하며, await이라는 일시 중단 함수를 통해 작업이 끝난 후 결과를 반환받을 수 있다. 네트워크 요청이나 데이터베이스에서 값을 읽어오는 등의 작업에 적합하다.
간단한 예시로 launch 와 async 의 차이를 살펴보자. 아래 코드에서 fetchDataAsync는 async로 실행되어 결과를 받을 수 있으며, await을 사용하여 결과를 동기 방식처럼 다룰 수 있다.
// launch 사용 예시
fun loadData() {
CoroutineScope(Dispatchers.Main).launch {
println("데이터를 로드합니다.") // 반환값 없이 실행
}
}
// async 사용 예시
fun fetchDataAsync() {
CoroutineScope(Dispatchers.IO).async {
val data = fetchData() // fetchData는 suspend 함수라고 가정
println("받아온 데이터: $data")
}
}
코루틴을 활용하여 안드로이드 앱의 비동기 작업을 효율적으로 처리하자.
참고로 안드로이드에서 코루틴을 사용하려면 먼저 kotlinx-coroutines-core 라이브러리를 프로젝트에 추가해야 한다. build.gradle 파일에 다음과 같이 의존성을 추가하면 된다
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'
}
최신 버전에 대한 정보는 아래 링크에서 확인할 수 있다.
https://github.com/Kotlin/kotlinx.coroutines/releases