시퀀스 빌더
시퀀스 빌더는 시퀀스를 만드는 도구이다. 시퀀스 빌더를 사용하면, 단계별로 데이터를 추가하면서 시퀀스를 구성할 수 있다. 예를 들어, yield 함수를 사용하면 하나의 요소를 시퀀스에 추가할 수 있고, yieldAll 함수를 사용하면 여러 요소를 한번에 추가할 수 있다.
간단히 말하면, 시퀀스 빌더는 단계별로 데이터를 추가하면서 '지연 계산'이 가능한 시퀀스를 만드는 도구이다.
시퀀스란
시퀀스는 코틀린에서 지원하는 특별한 종류의 컬렉션이다. 시퀀스의 주요 특징은 '지연 계산'이다.
즉, 시퀀스의 각 요소는 필요할 때까지 계산이 미루어지며, 이는 큰 데이터 셋을 다룰 때 메모리 사용량을 줄여준다.
※ 컬렉션(Collection) : 여러 개의 데이터를 그룹으로 관리할 수 있는 데이터 구조
특징
- 요구돠는 연산을 최소한으로 수행함
- 무한정이 될 수 있다.
- 메모리 사용이 효율적이다.
중단은 어떻게 작동할까?
중단 함수는 코틀린 코루틴의 핵심이다. 비디오 게임을 하다가 멈추는 상황이랑 비슷하다. 체크 포인트에서 게임을 저장하고 종료한 뒤, 사용자와 컴퓨터는 각자의 일을 하다가 다시 게임을 재개하고 저장한 체크포인트에서 시작할 수 있다. 이는 코루틴의 철학과 비슷하다.
코루틴은 중단되었을때 Continuation 객체를 반환한다. 이 객체는 게임을 저장하는것과 같다.
여기서 스레드와 많이 다른 것을 알 수 있는데, 스레드는 저장이 불가능하고 멈추는 것만 가능하다. 이러한 점에서 코루틴이 훨씬 강력하다. 코루틴이 중단 되었을 때 코루틴은 어떤 자원도 사용하지 않는다.
재개
작업이 재개되는 원리를 사용 예제를 통해 보자.
중단 함수는 말 그대로 코루틴을 중단 할 수 있는 함수이다. 이는 중단 함수가 반드시 코루틴에 의해 호출되어야 함을 의미한다.
아래의 간단한 예시가 있다.
suspend fun main() {
println("Before")
println("After")
}
//Before
//After
이 코드에 중간에 중단하면 어떻게 될까?
코틀린 라이브러리에서 제공하는 suspendCoroutine함수를 사용해보자.
suspendCoroutine의 람다 표현식 안에는 컨티뉴에이션의 객체를 인자로 받는다.
suspend fun main() {
println("Before")
suspendCoroutine<Unit> { continuation ->
println("Before too")
}
println("After")
}
//Before
//Before too
suspendCoroutine은 중단하기 전에 컨티뉴에이션의 객체를 사용할 수 있게 한다.
람다 함수는 컨티뉴에이션 객체를 저장한 뒤 코루틴을 다시 실행할 시점을 결정하기 위해 사용된다.
컨티뉴에이션 객체를 이용해 아래와 같이 곧바로 실행할 수 있다.
suspend fun main() {
println("Before")
suspendCoroutine<Unit> { continuation ->
continuation.resume(Unit)
}
println("After")
}
//Before
//After
suspendCoroutine에서 잠깐 동안 정지(sleep) 된 뒤 재개되는 다른 스레드를 실행시킬 수도 있다.
suspend fun main() {
println("Before")
suspendCoroutine<Unit> { continuation ->
thread {
println("Suspended")
Thread.sleep(1000)
continuation.resume(Unit)
println("Resumed")
}
}
println("After")
}
//Before
//Suspended
//(1초뒤)
//After
//Resumed
여기서 resume 함수에 Unit 을 인자로 넣는지 궁금할 것이다. 또 suspendCoroutine의 타입 인자로 Unit을 사용되는 이유도 궁금할 것이다. 이는 우연의 일치가 아니다. suspendCoroutine은 호출될때 컨티뉴에이션 객체로 반환될 값의 타입을 지정할 수 있다.
resume을 통해 반환 되는 값은 반드시 지정된 타입과 같은 타입이어야 한다.
이처럼 코루틴은 값으로 재게하는것이 자연스럽다. 이를 활용하면 API를 호출해 네트워크 응답을 기다리는 것처럼 특정 데이터를 기다리려고 중단하는 상황을 자주 발생한다. 스레드는 특정 데이터가 필요한 지점까지 비지니스 로직을 수행한다. 이후 네트워크 라이브러리를 통해 데이터를 요청한다. 코루틴이 없다면 스레드는 응답을 기다리고 있을 수밖에 없다. 이렇게 되면 엄청난 낭비이다.
그러나 코루틴이 있으면 중단함과 동시에 "데이터를 받고 나면, 받은 데이터를 resume 함수를 통해 보내줘."라고 컨티뉴에이션 객체를 통해 라이브러리에 전달 한다. 그러면 스레드는 다른 일을 할 수 있다. 그리고 데이터가 도착하면 스레드는 코루틴이 중단된 지점에서 재개된다.
아래 예제를 보자.
외부에 구현된 requestUser 콜백 함수를 사용해보자.
suspend fun main() {
println("Before")
val user = suspendCoroutine<User> { cont ->
requestUser { user ->
cont.resume(user)
}
}
println(user)
println("After")
}
// Before
// (1초뒤)
// User(name=Tapas, age=30)
// After
suspendCoroutine을 직접 호출 하는것은 불편하다. 대신 중단 함수를 호출하는 것이 낫다.
추출한 형태는 다음과 같다.
suspend fun requestUser(): User {
return suspendCoroutine<User> { cont ->
requestUser {
cont.resume(it)
}
}
}
suspend fun main() {
println("Before")
val user = requestUser()
println(user)
println("After")
}
참고로 중단함수는 Room과 Retrofit 같은 널리 사용되는 라이브러리에 의해 지원되고 있기때문에 중단함수내에서 콜백함수를 사용할 일은 거의 없다.
여기서 중요한 점은 함수가 아닌 코루틴을 중단시키는 점이다.
'도서 > 코틀린 코루틴' 카테고리의 다른 글
2장 코루틴 라이브러리 - Coroutine Buidler (0) | 2024.02.03 |
---|---|
코틀린 코루틴 이해하기 + 코루틴 실전 대입하기 (0) | 2024.01.23 |
1장 코틀린 코루틴 이해하기 - 코틀린 코루틴을 배워야 하는 이유 (0) | 2024.01.08 |
Retrofit 과 Coroutine 간의 Viewmodel에선 Dispacher.IO로 전환해야 할까? (1) | 2023.11.30 |
[DroidKnights 2023] 김준비 - Coroutine Deep Dive - Android 실전편 (0) | 2023.11.14 |