[Android(Kotlin)] kotlin 기초 - 3
카테고리: Android(Kotlin)
코루틴 (Coroutine)
코루틴은 코틀린 버전 1.3부터 정식으로 채택된 비동기 라이브러리로 운영체제에 의존적인 스레드와는 달리 스레드간 컨텍스트 전환 비용이 발생하지 않고 개발자가 직접 중지 지점을 선택할 수 있다는 특성이 있다.
코루틴을 이용한다면 기존의 복잡했던 비동기 로직(비동기 실행, 백그라운드 작업, 상황 업데이트, 완료 처리 등)을 아주 단순화 할 수 있다.
우선 코루틴을 사용하기 위해서 build.gradle에 코루틴 의존성을 추가한다.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
코루틴의 기본적인 사용법은 다음과 같다.
val scope = CoroutineScope(Dispatchers.Default)
scope.launch{
//do something
}
CoroutineScope의 확장 함수인 launch 빌더는 다음과 같이 어느 스케줄러에서 비동기 로직이 실행될지를 지정할 수 있다.
scope.launch(Dispatchers.IO){
// some I/O
}
서스펜딩(suspending) 함수
서스펜딩 함수는 현재 코루틴이 실행 중인 스레드를 블로킹하지 않으면서 실행 중인 코루틴을 잠시 중단시킬 수 있는 중단 지점 함수이다. 서스펜딩 함수를 호출하는 시점에 현재 실행 중인 코루틴은 잠시 중단되고 (남은 스레드는 다른 코루틴에 할당될 수 있다.) 서스펜딩 함수의 로직이 끝났을 때에 중단되었던 코루틴이 다시 실행 준비를 한다.
서스펜딩 함수는 suspend 키워드를 사용해 선언할 수 있다.
suspend fun someAsyncFunction() {
//do something
}
서스펜딩 함수의 이해를 돕기 위해 네트워크 호출을 통해 UI를 업데이트하는 예제를 살펴보자
suspend fun someNetworkCall() : String {
delay(1000)
return "data from network"
}
suspend fun uiFunction() {
val data = someNetworkCall()
println(data)
println("uiFunction is done")
}
uiFunction()을 호출하게 되면 내부적으로 someNetworkCall()을 호출하게 되고, 이 때 실행되고 있던 uiFunction()은 someNetworkCall()이 끝날 때까지 중단되게 된다. 하지만 uiFunction()을 실행하고 있던 스레드는 여기서 대기하는 것이 아니라 다른 코루틴에 할당될 수 있다. 앞의 예시를 수정해 확인해보자
suspend fun someNetworkCall() : String {
delay(1000)
return "data from network"
}
suspend fun uiFunction() {
println("uiFunction. ${Thread.currentThread().name}")
val data = someNetworkCall()
println(data)
println("uiFunction is done")
}
fun main() {
val scope = CoroutineScope(Dispatchers.Default)
scope.launch {
println("coroutine. ${Thread.currentThread().name}")
uiFunction()
}
Thread.sleep(500)
scope.launch {
println("another coroutine. ${Thread.currentThread().name}")
}
println("main is done. ${Thread.currentThread().name}")
Thread.sleep(2000)
}
이 예제 코드를 실행하면 다음과 같은 로그를 확인할 수 있다.
coroutine. DefaultDispatcher-worker-1
uiFunction. DefaultDispatcher-worker-1
main is done. main
another coroutine. DefaultDispatcher-worker-1
data from network
uiFunction is done
DefaultDispatcher-worker-1 이라는 스레드가 두 개의 코루틴을 모두 실행하는 것을 확인할 수 있다.
이번에는 두 개의 launch 빌더 호출 사이에 있는 Thread.sleep(500)
을 제거하고 실행해보자
coroutine. DefaultDispatcher-worker-1
uiFunction. DefaultDispatcher-worker-1
main is done. main
another coroutine. DefaultDispatcher-worker-2
data from network
uiFunction is done
DefaultDispatcher-worker-1이 uiFunction()을 실행하는 중이므로 두 번째 스레드는 다른 스레드가 할당되는 것을 확인할 수 있다.
이처럼 코루틴은 비동기 작업을 수행하면서 스레드를 블록킹하지 않고 재사용할 수 있기 때문에 더욱 효율적이고 빠르게 동작할 수 있다. 무엇보다 일반적인 함수 형태로 작성하고 호출이 가능하여 가독성이 높아지고 복잡도가 낮아지는 장점이 있다.
서스펜딩 함수는 코루틴 내부나 또다른 서스펜딩 함수 내부에서만 호출될 수 있으므로 서스펜딩 함수를 호출하기 위해서는 최소 하나의 코루틴 빌더 블록이 필요하다.
자주 사용되는 코루틴 빌더들은 다음과 같다.
-
runBlocking : 현재 스레드를 블로킹하는 코루틴 빌더. 내부의 서스펜딩 함수들도 모두 현재 스레드를 블로킹한다. 주로 메인 함수에서 탑레벨로 사용
-
launch : 현재 스레드를 블로킹하지 않고 새로운 비동기 작업을 시작하는 코루틴 빌더. 결과를 받을 수 없기 때문에 파이어 앤드 포겟(Fire-and-forget) 방식의 유즈케이스에 많이 사용. 예외가 전파되지 않으므로 블록 내부에 try-catch가 필요할 수 있다.
-
async : 현재 스레드를 블로킹하지 않고 새로운 배동기 작업을 시작하는 코루틴 빌더. Deferred 타입의 객체를 반환하며 await()를 호출해 결과값을 반환받을 수 있다. (await()는 서스펜딩 함수이기 때문에 코루틴 내부나 또 다른 서스펜딩 함수 내부에서 호출되어야 한다.) 예외가 전파되므로 블록 밖에서 try-catch로 예외 처리 가능