코틀린 스터디

셀렉트

막이86 2024. 1. 4. 10:53
728x90

코틀린 코루틴을 요약한 내용입니다.

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

💡 select 함수는 코틀린 코루틴이 정식으로 출시된 이후부터 사용이 가능했지만, 여전히 실험용

지연되는 값 선택하기

  • 여러 개의 소스에 데이터를 요청한 뒤, 가장 빠른 응답만 얻는 경우
    • 요청을 여러 개의 비동기 프로세스로 시작
    • select 함수를 표현식으로 사용하고 값을 기다린다
  • 비동기 결과값 하나만 반환하는 예제
    • 하나의 비동기 작업이 완료됨과 동시에 끝나게 되어 결과값 반환
    suspend fun requestData1(): String {
    	delay(100_000)
    	return "Data1"
    }
    
    suspend fun requestData2(): String {
    	delay(1000)
    	return "Data2"
    }
    
    val scope = CoroutinScope(SupervisioJob())
    
    suspend fun askMultipleForData(): String {
    	val defData1 = scope.async { requestData1() }
    	val defData2 = scope.async { requestData2() }
    	return select {
    		defData1.onAwait { it }
    		defData2.onAwait { it }
    	}
    }
    
    suspend fun main(): Unit = coroutineScope {
    	println(askMultipleForData())
    }
    
  • coroutinScope를 사용하면 자식 코루틴도 기다리게 됨
    • 1초가 아닌 100초 후에 Data2를 결과로 받을 수 있다
    suspend fun askMultipleForData(): String = coroutineScope {
    	select<String> {
    		async { requestData1() }.onAwait { it }
    		async { requestData2() }.onAwait { it }
    	}
    }
    
    suspend fun main(): Unit = coroutineScope {
    	println(askMultipleForData())
    }
    
  • async와 select를 사용하면 코루틴끼리 경합하는 상황을 쉽게 구현 가능
    • 스코프를 명시적으로 취소 해야함
    • select가 값을 생성하고 나서 also를 호출한 뒤 다른 코루틴을 취소
    suspend fun askMultipleForData(): String = coroutineScope {
    	select<String> {
    		async { requestData1() }.onAwait { it }
    		async { requestData2() }.onAwait { it }
    	}.also { coroutineContext.cancelChildren() }
    }
    
    suspend fun main(): Unit = coroutineScope {
    	println(askMultipleForData())
    }
    
  • raceOf 함수를 지원하는 외부 라이브러리를 사용하여 스코프를 명시적으로 취소해야하는 문제 해결
suspend fun askMultipleForData(): String = raceOf({
	requestData1()
}, {
	requestData2()
})

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

채널에서 값 선택하기

  • select 함수는 채널에서도 사용 가능
  • onReceive
    • 채널이 값을 가지고 있을 때 선택
    • 값을 받은 뒤 람다식의 인자로 사용
    • onReceive가 선택되었을 때, select는 람다식의 결과값을 반환
  • onReceiveCatching
    • 채널이 값을 가지고 있거나 닫혔을 때 선택
    • 값을 나타내거나 채널이 닫혔다는 걸 알려주는 ChannelResult를 받으며, 이 값을 람다식의 인자로 사용
    • onReceiveCatching이 선택되었을 때, select는 람다식의 결과를 반환
  • onSend
    • 채널의 버퍼에 공간이 있을 때 선택
    • 채널에 값을 보낸 뒤, 채널의 참조값으로 람다식을 수행
    • onSend가 선택되었을 때, select는 Unit을 반환
  • 셀렉트 표현식은 여러 개의 채널로부터 결과값을 얻기 위해 onReceive나 onReceiveCatching과 함께 사용
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()
}
  • 셀렉트 함수에서 onSend를 호출하면 버퍼에 공간이 있는 채널을 선택해 데이터를 전송하는 용도로 사용 할 수 있음
fun main(): Unit = runBlocking {
	val c1 = Channel<Char>(capacity = 2)
	val c2 = Channel<Char>(capacity = 2)

	launch {
		for (c in 'A'..'H') {
			delay(400)
			select<Unit> {
				c1.onSend(c) { println("Sent $c to 1") }
				c2.onSend(c) { println("Sent $c to 2") }
			}
		}
	}

	launch {
		while (true) {
			delay(1000)
			val c = select<String> {
				c1.onReceive { "$it from 1" }
				c2.onReceive { "$it from 2" }
			}
			println("Received $c")
		}
	}
}

요약

  • select는 가장 먼저 완료되는 코루틴의 결과값을 기다릴 때나 여러 개의 채널 중 전송 또는 수신 가능한 채널을 선택할 때 유용
  • 채널에서 작동하는 다양한 패턴을 구현할 때 사용 가능
  • async 코루틴의 경합을 구현할 때 사용 가능
728x90

'코틀린 스터디' 카테고리의 다른 글

플로우란 무엇인가?  (1) 2024.01.04
핫 데이터 소스와 콜드 데이터 소스  (0) 2024.01.04
채널  (0) 2023.12.04
코루틴 스코프 만들기  (1) 2023.11.24
코루틴 기반 동시성 프로그래밍  (0) 2023.11.24