[Android] Flow의 emit은 왜 suspend함수일까?
Kotlin에서 비동기 스트림을 처리할 때 사용하는 Flow는 간결하고 강력한 API를 제공합니다. 특히 Flow를 사용할 때 자주 등장하는 emit() 함수가 눈에 띄는데요, 문서나 코드에서 보면 emit()이 suspend 함수로 정의되어 있다는 것을 알 수 있습니다.
그렇다면, 왜 emit()은 일반 함수가 아니라 suspend 함수로 설계되었을까요?
결론 부터 말하면, Backpressure(역압) 때문인데요.
그 이유 대해서 자세히 살펴보도록 하겠습니다.
emit()이 하는 일은?
간단히 말해, emit()은 Flow 내부에서 데이터를 외부로 발행(emit) 하는 역할을 합니다.
flow {
emit(1)
emit(2)
emit(3)
}
위 코드는 1, 2, 3이라는 값을 순차적으로 소비자에게 보내는 역할을 하죠.
왜 suspend여야 할까?
생산자와 소비자의 속도 불균형: Backpressure
Flow는 생산자(emit) 와 소비자(collect) 가 동시에 동작합니다. 하지만 이 둘의 속도가 항상 같지는 않습니다.
예를 들어:
flow {
for (i in 1..1_000_000) {
emit(i)
}
}.collect {
delay(1000)
println(it)
}
- 생산자는 아주 빠르게 100만 개의 데이터를 발행
- 소비자는 1초에 하나씩만 처리
만약 emit()이 suspend가 아니라 그냥 데이터를 던지는 함수였다면, 소비자가 따라오지 못하는 데이터는 전부 메모리에 쌓이게 됩니다.
결과적으로 아래와 같은 문제가 생기죠
- OutOfMemoryError 발생 가능
- 앱 성능 저하
- 기기의 배터리 소모 가속화
해결책: emit()을 suspend로 만들어라!
suspend 키워드를 통해 emit()은 소비자가 데이터를 처리할 때까지 기다릴 수 있는 능력을 가집니다.
즉,
- 소비자가 느리면 생산자는 멈추고 기다림
- 메모리에 무분별하게 데이터를 쌓지 않음
- 생산-소비 속도에 맞는 자연스러운 흐름 형성
생산자-소비자 간의 속도 불균형을 조절하는 것이 바로 Backpressure 처리입니다. 그리고 emit()이 suspend인 이유는 이 backpressure 문제를 안전하게 처리하기 위해서입니다.
함께 알아두면 좋은 것들
- channelFlow와 같은 고급 Flow 빌더에서는 emit()이 내부적으로 채널에 데이터를 전송하기 때문에 더 명확하게 suspend가 필요한 상황이 드러납니다.
- Flow는 기본적으로 cold stream이며, 데이터를 실제로 소비할 때 실행되므로 suspend 기반이 잘 어울립니다.
마무리
Kotlin의 emit()이 suspend인 이유는 단순한 문법적 선택이 아니라, 안정적이고 예측 가능한 비동기 스트림 처리를 위한 핵심 설계입니다. 특히 생산자와 소비자 간의 처리 속도 차이를 고려한 Backpressure 제어는 고성능 앱 개발에서 매우 중요한 요소입니다.