일시 중단 함수들은 비동기적으로 단일 값을 반환한다. 그렇다면 어떻게 비동기적으로 계산된 복수의 값들을 반환할 수 있을까? 여기에서 바로 Kotlin의 Flows가 등장한다.
복수의 값들 표현하기
Kotlin에서 복수의 값들은 Collections를 사용해 표현될 수 있다. 예를 들어 3개의 숫자를 가진 List를 반환하는 simple 함수를 가지고, forEach를 사용해 그들을 모두 프린트할 수 있다.
fun simple(): List<Int> = listOf(1, 2, 3)
fun main() {
simple().forEach { value -> println(value) }
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이 코드의 출력은 다음과 같다 :
1
2
3
Sequences
만약 CPU 리소스를 사용하면서 블로킹을 하는 코드*1(각 연산은 100ms의 시간이 소요된다)로 숫자에 대한 연산을 한다면, Sequence를 사용해 숫자를 나타낼 수 있다.
fun simple(): Sequence<Int> = sequence { // sequence builder
for (i in 1..3) {
Thread.sleep(100) // pretend we are computing it
yield(i) // yield next value
}
}
fun main() {
simple().forEach { value -> println(value) }
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이 코드의 결과값은 위와 같지만, 각 숫자들을 프린트하기 전 100ms을 대기한다.
일시중단 함수들
그러나 이러한 연산은 코드를 실행하는 메인 스레드를 블로킹한다. 만약 이 값들이 비동기 코드에 의해 계산된다면, 스레드를 블로킹 시키지 않고 수행되고 결과값을 List로 반환할 수 있도록 simple 함수를 suspend 수정자로 표시할 수 있다.
suspend fun simple(): List<Int> {
delay(1000) // pretend we are doing something asynchronous here
return listOf(1, 2, 3)
}
fun main() = runBlocking<Unit> {
simple().forEach { value -> println(value) }
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이 코드는 1초간 기다린 후 숫자들을 프린트한다.
Flows
결과 타입으로 List<Int>를 사용하면 한 번에 모든 값을 반환해야만 한다. 동기적으로 계산된 값을 Sequence를 사용해 나타냈던 것처럼, 비동기적으로 계산되는 값들을 스트림으로 나타내기 위해서는 Flow<Int> 타입을 사용할 수 있다 :
fun simple(): Flow<Int> = flow { // flow builder
for (i in 1..3) {
delay(100) // pretend we are doing something useful here
emit(i) // emit next value
}
}
fun main() = runBlocking<Unit> {
// Launch a concurrent coroutine to check if the main thread is blocked
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
// Collect the flow
simple().collect { value -> println(value) }
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이 코드는 각 숫자들을 프린트하기 전, 메인 스레드를 블로킹 하지 않고 100ms 동안 대기한다. 이는 "I'm not blocked"를 100ms 마다 프린트하는 메인 스레드에서 실행되는 별도의 Coroutine을 통해 확인된다 :
I'm not blocked 1
1
I'm not blocked 2
2
I'm not blocked 3
3
이전 예제들에서의 코드들과 Flow는 다음의 차이점들이 있다는 것을 확인하자 :
- Flow의 빌더 함수는 flow이다.
- flow { ... } 블록 내부의 코드들은 일시 중단 될 수 있다.
- simple 함수는 더이상 suspend 수정자로 표시되어 있지 않다.
- emit 함수를 사용해 flow에서 값들이 방출*2된다.
- collect 함수를 사용해 flow로부터 값들을 수집*3한다.
📖 simple 함수의 flow { ... } 블록 내부에서 delay를 Thread.sleep으로 교체하는 경우*4 메인 스레드가 블록되는 것을 볼 수 있다.
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. 연산이 오래 걸려서 스레드가 블로킹 되는 경우를 뜻한다.
*2. 리액티브 프로그래밍의 발행을 뜻한다.
*3. 리액티브 프로그래밍의 구독을 뜻한다.
*4. 다음과 같이 변경 하는 것을 뜻한다.
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
Thread.sleep(100)
emit(i)
}
}
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Asynchronous Flow - Representing multiple values
원문 최종 수정 : 2022년 9월 28일
Flows는 차갑다
Flow는 Sequence와 비슷한 차가운 Stream*1이다. flow 빌더 내부의 코드는 flow가 collect되기 전까지 실행되지 않는다. 이는 다음의 예에서 확실히 나타난다.
fun simple(): Flow<Int> = flow {
println("Flow started")
for (i in 1..3) {
delay(100)
emit(i)
}
}
fun main() = runBlocking<Unit> {
println("Calling simple function...")
val flow = simple()
println("Calling collect...")
flow.collect { value -> println(value) }
println("Calling collect again...")
flow.collect { value -> println(value) }
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이는 다음과 같은 출력을 가진다 :
Calling simple function...
Calling collect...
Flow started
1
2
3
Calling collect again...
Flow started
1
2
3
이것이 flow를 반환하는 simple 함수가 suspend 수정자로 표시되지 않은 이유이다. simple() 함수 호출 그 자체는 곧바로 반환되며 어떤 것도 기다리지 않는다. flow는 collect가 될때마다 새로 시작되며, 이것이 collect를 다시 호출할 때마다 "Flow started"가 표시되는 이유이다.
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. flow는 리액티브 프로그래밍의 발행자 유형 중, Cold Stream에 속한다. Cold Stream은 차가운 Stream이라고도 불리며 구독이 일어나기 전까지 블록 내부 코드가 실행되지 않고 구독이 일어날 경우에 블록 내부의 코드가 실행되는 Stream을 지칭한다. 반대로 Hot Stream은 구독이 없어도 코드가 실행된다.
주의할 점은 Cold와 Hot Stream 나누는 기준은 코드의 실행이지 발행이 아니라는 점이다. 예를 들어 Hot Stream인 StateFlow는 발행된 값들 중 마지막 값을 구독 즉시 발행하지만, 코드를 실행해 발행 값을 업데이트 하지는 않는다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Asynchronous Flow - Flows are cold
원문 최종 수정 : 2022년 9월 28일
목차로 돌아가기
'공식 문서 번역 > Coroutines 공식 문서' 카테고리의 다른 글
Coroutines Flow 3편 - Flow 터미널 연산자, Flow는 순차적이다 (0) | 2023.02.27 |
---|---|
Coroutines Flow 2편 - Flow 취소하기, Flow 빌더, Flow 중간 연산자 (0) | 2023.02.26 |
Coroutine Context와 Dispatcher 5편 - Coroutine Scope (0) | 2023.02.24 |
Coroutine Context와 Dispatcher 4편 - 부모 Coroutine의 책임, Coroutines에 이름 짓기, Context 요소들 결합하기 (0) | 2023.02.23 |
Coroutine Context와 Dispatcher 3편 - Thread 전환 하기, Context 내부의 Job, Coroutine의 자식들 (0) | 2023.02.22 |