Android Weekly

Android Weekly #-605 "Flow 와 ChannelFlow 의 비교"

베블렌 2024. 1. 25. 20:40

1월 3주차에는 flow 와 channelFlow의 비교입니다.

 

https://proandroiddev.com/why-use-flow-if-we-have-the-powerful-channelflow-in-mobile-development-1e8e718c80ea

 

Why use Flow if we have the powerful ChannelFlow in mobile development?

What can we get from Flow what ChannelFlow cannot get us?

proandroiddev.com

 

 

소개

이 글은 코루틴 컨텍스트에서 Flow 와 ChannelFlow 의 비교글입니다.

 

 

내용

  • 핵심 개념: Channel은 코루틴 간의 통신을 위해 설계되었으며, 다수의 소스에서 동시에 데이터를 방출하는 것을 용이하게 합니다. 반면, Flow는 데이터 생성 및 배포에 중점을 둡니다.

 

ChannelFlow 사용 사례

 

ChannelFlow는 여러 코루틴에서 동시에 데이터를 방출할 필요가 있는 복잡한 시나리오에 적합합니다. 예를 들어, LAN, 블루투스, 네트워크 헬퍼를 통해 동시에 다양한 소스에서 데이터를 수집하고 이를 하나의 스트림으로 통합할 때 ChannelFlow를 사용할 수 있습니다. 이는 각 데이터 소스가 자체 코루틴에서 실행되며 동일한 채널로 값을 방출한다는 것을 의미합니다.

val nearbyDevices = channelFlow {
    launch {
        send(LANHelper.discoverConnectableHosts())
    }

    launch {
        send(BluetoothHelper.discoverConnectableDevices())
    }

    launch {
        send(NetworkHelper.getRegisteredDevices())
    }
}

// At the place you need the data
nearbyDevices.collect { data ->
    // Do something
}

 

위의 코드는 LAN, 블루투스, 네트워크 헬퍼를 통해 주변 장치를 발견하고 이를 하나의 스트림으로 통합하는 과정을 보여줍니다. 이 경우, 각 데이터 소스가 자체 코루틴에서 실행되며 동일한 채널로 값을 방출합니다.

 

 

Flow를 사용한 유사한 동작 구현

 

Flow를 사용하여 유사한 동작을 구현하는 것도 가능합니다. 이 경우, 각 네트워크 요청과 블루투스 탐색 작업을 별도의 Flow로 분할하고, 특별한 함수(merge)를 사용하여 데이터를 결합할 수 있습니다.

fun networkDataFlow() = flow {
    emit(LANHelper.discoverConnectableHosts())
}

fun bluetoothDiscoveryFlow() = flow {
    emit(BluetoothHelper.discoverConnectableDevices())
}

fun registeredDevicesNearbyFlow() = flow {
    emit(NetworkHelper.getRegisteredDevices())
}

// 데이터를 사용할 위치
merge(
    networkDataFlow(),
    bluetoothDiscoveryFlow(),
    registeredDevicesNearbyFlow()
).collect { combinedData ->
    // 데이터에 반응
    println(combinedData)
}

 

이 방법은 각 데이터 소스로부터 독립적으로 데이터를 방출하고 결과적으로 하나의 스트림으로 합치는 과정을 포함합니다.

 

아키텍처 차이 비교

 

여기서의 주요 차이점은 아키텍처 수준에서 발생합니다. ChannelFlow는 동시에 여러 코루틴에서 데이터를 방출할 수 있는 능력을 제공하는 반면, Flow는 각 소스로부터 독립적으로 데이터를 방출하고 이를 결합하는 더 단순한 접근 방식을 사용합니다. 간단한 애플리케이션에서는 두 접근 방식 모두 비슷한 결과를 제공할 수 있지만, 고속 데이터 방출이 필요하지 않은 경우에 한합니다.

 

성능 비교

 

성능 측면에서 글쓴이는 Flow와 ChannelFlow의 성능을 비교하여, Flow가 일반적으로 더 빠르다는 것을 발견했습니다. 특히, 소규모 데이터 방출(예: 1000개의 이벤트)에서 Flow는 ChannelFlow보다 더 낮은 지연 시간을 가지며, 데이터 방출의 규모가 커질수록 이 차이는 더욱 확대됩니다. 그러나 실제 모바일 개발에서 이러한 데이터 시나리오는 드물기 때문에, 성능보다는 아키텍처적 적합성이 더 중요한 고려 사항이 될 수 있습니다.

결론적으로, ChannelFlow와 Flow 간의 선택은 특정 애플리케이션의 요구 사항, 특히 데이터 방출 소스의 복잡성과 동시성 요구 사항에 따라 달라질 수 있습니다. Flow는 일반적으로 더 간단하고 빠른 성능을 제공하는 반면, ChannelFlow는 더 복잡한 동시 데이터 처리 시나리오에 적합할 수 있습니다.

flow
1000      -> 0.012513591
10000     -> 0.091140584
100000    -> 0.411128418
1000000   -> 8.215107670
10000000  -> 40.605143168
100000000 -> 403.867435622

channelFlow
1000      -> 0.040344645
10000     -> 0.300058228
100000    -> 12.407572719
1000000   -> 106.307522798
10000000  -> 310.608283640
I stopped waiting after an hour of calculations :)

 

 

실제 모바일 개발 적용성

 

대부분의 모바일 앱에서는 초당 수백, 수천 번의 데이터 방출이 일어나지 않습니다. 따라서 ChannelFlow의 성능 저하가 모바일 앱의 전반적인 성능에 미치는 영향은 제한적일 수 있습니다.

ChannelFlow의 주요 장점은 복잡한 데이터 스트림 처리와 동시 데이터 방출을 필요로 하는 경우에 있습니다. 예를 들어, 여러 데이터 소스에서 비동기적으로 데이터를 생성하고 이를 통합해야 하는 경우에 유용합니다.

 

ChannelFlow의 독특한 기능

 

ChannelFlow는 개발자가 코루틴의 생명 주기를 독립적으로 제어할 수 있게 합니다. 이를 통해 런타임에 하드웨어 부하를 유연하게 조절할 수 있습니다.

다음 코드 예제는 ChannelFlow를 사용하여 두 개의 코루틴이 값을 방출하는 상황을 보여줍니다. 첫 번째 코루틴이 특정 값을 방출하면, 새로운 코루틴이 시작되어 추가적인 값을 생성합니다. 이 예에서는 ChannelFlow가 어떻게 복잡한 데이터 스트림 처리를 가능하게 하는지 보여줍니다.

 

suspend fun launchAdditionalProducer(scope: ProducerScope<String>) {
    scope.launch {
        repeat(5) { index ->
            delay(50)
            scope.send("Message $index from Main Additional Producer")
        }
    }
}

fun mainFlow() {
    val channel = channelFlow {
        launch {
            repeat(20) { index ->
                delay(100)
                send("Message $index from Main Producer")
                if (index == 2) {
                    launchAdditionalProducer(this@channelFlow)
                }
                if (index >= 5) {
                    cancel()
                }
            }
        }
        launch {
            repeat(20) { index ->
                delay(33)
                send("Message $index from Secondary Producer")
            }
        }
    }
    CoroutineScope(Dispatchers.Default).launch {
        channel.collect { value ->
            println(value)
        }
    }
}

 

이 예시에서는 ChannelFlow를 통해 데이터를 비동기적으로 생성하고, 코루틴의 생명 주기를 개별적으로 제어할 수 있는 방법을 보여줍니다. 이는 Flow와 달리 복잡한 데이터 처리 시나리오에서 개발자에게 더 많은 유연성을 제공합니다.

 

 

정리

Flow는 일반적으로 고속 데이터 방출에 대해 더 빠르고 단순하지만, ChannelFlow는 복잡한 데이터 처리, 코루틴 동작에 대한 미세한 제어, 그리고 다수의 소스로부터의 동시 방출을 요구하는 시나리오에서 장점이 있습니다. Flow와 ChannelFlow 사이의 선택은 애플리케이션의 특정 요구 사항에 의해 안내되어야 하며, 방출 속도, 데이터 처리 복잡성 및 코루틴 동작에 대한 세밀한 제어의 필요성을 고려해 선택해야합니다.

 

 

 

 

https://androidweekly.net/

 

Android Weekly - Free weekly Android & Kotlin development newsletter

Android Weekly - Free weekly Android & Kotlin development newsletter

androidweekly.net