[Kotlin] Coroutine의 Channel 과 Flow

sundays·2023년 4월 4일
0

coroutine

목록 보기
1/7

이번에 새로 TO-DO앱을 작성하는데 Channel을 사용하고 있습니다. 계속해서 데이터가 들어와 출력해야 하는 Snackbar에서 Channel을 사용하고 있습니다. 예를들면 MutableList<> 와 같은 것들은 한번에 데이터를 가져오게 되는데, 이러한 경우가 아닌 데이터들이 실시간으로 가져와서 처리해야 할때 Channel 또는 Flow를 사용합니다.

Channel

채널은 한쪽에서 값을 보내는 메서드(send) 다른 쪽에서 받는(receive) 메서드를 사용해서 사용합니다. BlockingQueue와 비슷한 컨셉이라고 합니다.

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*

fun main() = runBlocking {
    val channel = Channel<Int>()
    launch {
        // this might be heavy CPU-consuming computation or async logic,
        // we'll just send five squares
        for (x in 1..5) channel.send(x * x)
    }
    // here we print five received integers:
    repeat(5) { println(channel.receive()) }
    println("Done!")
}

[결과]

1
4
9
16
25
Done!

기본적으로 send()receive()로 데이터를 주고 받을 수 있습니다만 for-loop를 통해서 데이터를 receive()를 대신할 수도 있습니다.

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*

fun main() = runBlocking {
    val channel = Channel<Int>()
    launch {
        for (x in 1..5) channel.send(x * x)
        channel.close() // we're done sending
    }
    // here we print received values using `for` loop (until the channel is closed)
    for (y in channel) println(y)
    println("Done!")
}

[결과]

1
4
9
16
25
Done!

위의 코드와 다른점은 channel.close() 부분이 추가되었다는 것인데요. 이부분이 없다면 Done!을 출력하지 않으며 Channel pipeline이 종료되지 않습니다.

producer-consumer 패턴을 이용할 수도 있는데, produce builder를 이용한 코루틴으로 작성합니다. consumeEach 가 for-loop를 대신하여 작성할 수 있습니다.

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*

fun CoroutineScope.produceSquares(): ReceiveChannel<Int> = produce {
    for (x in 1..5) send(x * x)
}

fun main() = runBlocking {
    val squares = produceSquares()
    squares.consumeEach { println(it) }
    println("Done!")
}

[결과]

1
4
9
16
25
Done!

Flow

Flow의 경우에는 Channel과는 다르게 가져오는 시점을 지정할 수 있습니다. 채널의 경우에는 데이터가 선언 시점부터 존재 합니다.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun simple(): Flow<Int> = flow {
    println("Flow started")
    for (i in 1..3) {
        delay(100)
        emit(i * 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
4
9
Calling collect again...
Flow started
1
4
9

collect하는 시점부터 flow가 차례대로 수행됩니다

flow builder

flow builder로 collect를 할 수도 있습니다

(1..5).asFlow().collect { value -> println(value * value) }

[결과]

1
4
9
16
25

map을 사용해서 value를 가공할 수도 있습니다.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

suspend fun performRequest(request: Int): String {
    delay(1000) // imitate long-running asynchronous work
    return (request * request).toString()
}

fun main() = runBlocking<Unit> {
    (1..5).asFlow() // a flow of requests
        .map { request -> performRequest(request) }
        .collect { response -> println(response) }
}

[결과]

1
4
9
16
25

결론

Channel와 Flow는 데이터를 가져오는 시점에 따라 차이가 있습니다. Flow 에서는 데이터를 가져오는 시점이 사용자가 호출하는 시점입니다. 원하는 시점에 가져오게 되면 Cold, 호출을 하기도 전에 데이터를 가져오는 경우는 Hot이라고 한다고 합니다.

Channel을 receiveAsFlow()로 받으면 channel을 마치 Flow처럼 가져오는 데이터를 사용하게 할 수도 있습니다. 이부분을 viewmodel에 작성하였는데요. 이부분은 저의 개인 깃헙에 정리하였습니다. 다음 포스팅에는 sharedFlow 와 같이 비교해보겠습니다

Reference

profile
develop life

0개의 댓글