- 플로우는 비동기적으로 계산해야 할 값의 스트림을 나타냄.
- Flow 인터페이스 자체는 떠다니는 원소들을 모으는 역할이며, 플로우 끝에 도달할 때까지 각 값을 처리하는걸 의미함.
- Flow의
collect
는 컬렉션의 forEach
와 비슷함.
interface Flow<out T> {
suspend fun collect(collector: FlowCollector<T>)
}
- Flow의 유일한 멤버 함수는
collect
임.
iterator
만 멤버 함수로 가지고 있는 Iterable
, Seqeunce
와 비슷함.
플로우와 값들을 나타내는 다른 방법들의 비교
- 여러 개의 값을 반환하는 함수가 필요하다고 가정하고, 한번에 모든 값을 만들 땐
List
, Set
과 같은 컬렉션을 사용함.
- 명심해야 할 점은 List와 Set이 모든 원소의 계산이 완료된 컬렉션임.
- 원소를 하나씩 계산할 때는, 원소가 나오자마자 바로 얻을 수 있는
Sequence
를 사용하는 것이 방법임.
- 시퀀스는 데이터 소스의 개수가 많거나(또는 무한정), 원소가 무거운 경우, 원소를 필요할 때만 계산하는 상황에 어울림.
- 플로우는 코루틴을 사용해야 하는 데이터 스트림으로 사용되어야 함.
- API 페이지에서 페이지별로 사용자을 얻은 뒤 사용자 스트림을 만든다면
- 첫 번째 페이지 :
allUserFlow(api).first()
- 모든 페이지 :
allUserFlow(api).toList()
- 원하는 사용자 :
allUsersFlow(api).find { [it.id](<http://it.id>) == id }
플로우의 특징
- 플로우의 최종 연산은 스레드를 블로킹하는 대신 코루틴을 중단 시킴.
- 플로우는 코루틴 컨텍스트를 활용하고 예외를 처리하는 등 코루틴 기능도 제공함.
- 플로우 처리는 취소 가능하며, 구조화된 동시성을 기본적으로 갖추고 있음.
- 플로우 빌더는 중단 함수가 아니며 어떠한 스코프도 필요하지 않음.
- 플로우의 최종 연산은 중단 가능하며, 연산이 실행될 때 부모 코루틴과의 관계가 정립됨.
fun usersFlow(): Flow<String> = flow {
repeat(3) {
delay(1000)
val ctx = currentCoroutineContext()
val name = ctx[CoroutineName]?.name
emit("User$it in $name")
}
}
suspend fun main() {
val users = usersFlow()
withContext(CoroutineName("Name")) {
val job = launch {
users.collect { it ->
println(it)
}
}
launch {
delay(2100)
println("I got enough")
job.cancel()
}
}
}
실행결과 :
(1초후)
User0 in Name
(1초후)
User1 in Name
(0.1초후)
I got enough
궁금한 부분
- p.262 ~ 264 시퀀스와 플로우의 각각 실행 결과가 다른데, 이유는 시퀀스는 단일 스레드를 사용해서 스레드 먼저 동기식으로 처리?