관리 메뉴

나만을 위한 블로그

[코틀린 코루틴] 17. 셀렉트 본문

책/코틀린 코루틴

[코틀린 코루틴] 17. 셀렉트

참깨빵위에참깨빵 2024. 4. 6. 21:30
728x90
반응형

코루틴은 가장 먼저 완료되는 코루틴의 결과를 기다리는 select 함수를 제공한다.

또한 여러 채널 중 버퍼에 남은 공간이 있는 채널을 먼저 확인해서 데이터를 보내거나 이용 가능한 원소가 있는 채널로부터 데이터를 받을 수 있는지 확인할 수 있다.

 

지연되는 값 선택

 

여러 소스에 데이터를 요청한 뒤 가장 빠른 응답만 얻는 경우를 생각한다. 가장 쉬운 방법은 요청을 여러 비동기 프로세스로 시작하고 select 함수를 표현식으로 사용하고, 표현식 안에서 값을 기다리는 것이다.

select 안에선 select 표현식에서 나올 수 있는 결과값을 명시하는 Deferred 값의 onAwait()를 호출한다. 람다 안에서 값을 바꿀 수도 있다.

아래 예에서 비동기 결과값 하나만 리턴하는 걸 볼 수 있는데 select 표현식이 하나의 비동기 작업이 완료됨과 동시에 끝나서 결과값을 리턴하는 걸 알 수 있다.

 

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.selects.select

suspend fun main(): Unit = coroutineScope {
    println(askMultipleForData())
}

suspend fun requestData1(): String {
    delay(100_000)
    return "Data1"
}

suspend fun requestData2(): String {
    delay(1000)
    return "Data2"
}

val scope = CoroutineScope(SupervisorJob())

suspend fun askMultipleForData(): String {
    val defData1 = scope.async { requestData1() }
    val defData2 = scope.async { requestData2() }

    return select {
        defData1.onAwait { it }
        defData2.onAwait { it }
    }
}

// (1초 후)
// Data2

 

위 예에선 외부 스코프로부터 async가 시작된다. 따라서 askMultipleForData를 시작하는 코루틴을 취소하면 외부 스코프인 비동기 태스크는 취소되지 않는다.

coroutineScope를 쓰면 자식 코루틴도 기다리게 되며 아래처럼 100초 후에 Data2를 결과로 받는다.

 

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.selects.select

suspend fun main(): Unit = coroutineScope {
    println(askMultipleForData())
}

suspend fun requestData1(): String {
    delay(100_000)
    return "Data1"
}

suspend fun requestData2(): String {
    delay(1000)
    return "Data2"
}

val scope = CoroutineScope(SupervisorJob())

suspend fun askMultipleForData(): String = coroutineScope {
    select {
        async { requestData1() }.onAwait { it }
        async { requestData2() }.onAwait { it }
    }
}

// (100초 후)
// Data2

 

채널에서 값 선택하기

 

select 함수는 채널에서도 쓸 수 있다. select 표현식에서 쓰는 주요 함수는 아래와 같다.

 

  • onReceive : 채널이 값을 갖고 있을 때 선택됨. receive()처럼 값을 받고 람다 인자로 사용함. onReceive가 선택됐을 때 select는 람다의 결과값을 리턴
  • onReceiveCatching : 채널이 값을 갖고 있거나 닫혔을 때 선택됨. 값을 나타내거나 채널이 닫힌 걸 알려주는 ChannelResult를 받고 이 값을 람다 인자로 사용함. onReceiveCatching이 선택됐을 때 select는 람다의 결과값을 리턴
  • onSend : 채널 버퍼에 공간이 있을 때 선택됨. 채널에 값을 보낸 뒤 채널의 참조값으로 람다를 수행함. onSend가 선택되면 select는 Unit을 리턴

 

select 표현식은 여러 채널로부터 결과값을 얻기 위해 onReceive나 onReceiveCatching과 같이 쓰인다.

 

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.selects.select

suspend fun CoroutineScope.produceString(
    s: String,
    time: Long
) = produce {
    while (true) {
        delay(time)
        send(s)
    }
}

fun main() = runBlocking {
    val fooChannel = produceString("foo", 210L)
    val barChannel = produceString("BAR", 500L)

    repeat(7) {
        select {
            fooChannel.onReceive {
                println("From fooChannel : $it")
            }
            barChannel.onReceive {
                println("From barChannel : $it")
            }
        }
    }

    coroutineContext.cancelChildren()
}

// From fooChannel : foo
// From fooChannel : foo
// From barChannel : BAR
// From fooChannel : foo
// From fooChannel : foo
// From barChannel : BAR
// From fooChannel : foo

 

반응형
Comments