문제
2번 문제는 각 Coroutine에서 Emit이 발생하고 각각의 SharedFlow의 구독이 발생할 때 들어온 값 맞추기이다.
Shared Flow가 추가된 것 말곤는 1번과 크게 다르지 않다.
// file: "문제 2번.kt"
class Step2 {
@Test
fun `SharedFlow replay`() = runTest {
// given
val actual: StringBuilder = StringBuilder()
val sharedFlow = MutableSharedFlow<Int>(replay = 1)
// when
val emitterJob = launch {
sharedFlow.emit(1)
delay(100)
sharedFlow.emit(2)
delay(100)
sharedFlow.emit(3)
delay(100)
sharedFlow.emit(4)
}
val collectorJob1 = launch {
sharedFlow.collect(actual::append)
}
delay(150)
val collectorJob2 = launch {
sharedFlow.collect(actual::append)
}
emitterJob.join()
collectorJob1.cancelAndJoin()
collectorJob2.cancelAndJoin()
// then
val expected = "" // TODO: 결과값 예상
/*
TODO: 간단한 풀이과정 작성
*/
// assert문 수정하지 마세요!
assertHashcode(actual, expected)
}
}
풀이
간단하게 replay에 대한 것만 이해하면 되는데, SharedFlow의 replay란 마지막으로 Emit된 값을 저장할 개수이다.
만약 구독하기 전에 emit되어서 값을 받지 못한 경우를 위해 설정하는 것이다.
이 문제 역시 우선 크게 코루틴을 분리해보자
runTest를 제외하고, 위에서부터 차례대로
SharedFlow에서 값을 발행하는 코루틴
SharedFlow를 구독하는 코루틴
딜레이
SharedFlow를 구독하는 코루틴 이 있다.
이 파일을 실행하게 된다면
emit 코드가 들어있는 코루틴 시작
구독하는 코루틴 시작
delay 기다리기
구독하는 코루틴 시작 정도가 된다.
조금 더 구체적으로는
emitterJob 코루틴이 실행되면서 **emit(1)을 진행한다..
collectorJob1이 실행되고, emitterJob은 delay가 실행되면서 점유를 내준다. 그리고 main도 delay가 실행되면서 점유를 내준다.
이 때 replay가 1이므로 이미 앞서 collectJob1의 collect로 emit되었던 1이 들어온다.
100ms 이후 emit(2)가 진행되고 collectJob1의 collect로 2가 들어온다.
다시 emitterJob은 delay가 실행되면서 점유를 내주고 기다리는 동안 Main의 delay가 끝나 collectorJob2가 실행된다.
replay를 1로 설정했으므로 이미 2는 collectorJob2가 구독하기 이전에 발행된 이벤트이지만 collectorJob2의 collect로 2가 들어온다.
join을 통해서 emit 작업이 다 끝날 때까지 emitterJob에서 delay를 하더라도 기다리게 된다.
그 동안 3과 4가 emit되어서 collectorJob1과 collectorJob2에 각각 값이 들어오게 된다.
그리고 emitterJob이 끝나고, 구독을 cancelAndJoin()을 실행해 취소 요청을 보내고 종료될 때까지 기다리며 종료한다.
마지막으로 값을 비교한다.
순서상으로 actual 값을 정리하자면
collectJob1에서 replay로 1을 수신 => actual = “1”
collectJob1에서 collect로 2를 수신 => actual = “12”
collectJob2에서 replay로 2를 수신 => actual = “122”
collectJob1과 collectJob2에서 collect로 3과 4를 수신 => actual = “1223344”
즉 정답은 “1223344”가 된다.
번외
문제는 풀었지만 코루틴에 대해서 잘 모른다면 생각해볼 만한 부분이 있다.
cancelAndJoin을 호출한 이유는?
만약 collectorJob2 생성 윗 부분에서 delay를 주지 않았다면?
크게 두개로 나눌 수 있을 듯 하다.
cancelAndJoin을 호출하지 않았다면?
앞서 설명했다시피 cancelAndJoin은 코루틴의 종료를 요청하고 기다리는 함수이다.
만약 종료를 하지 않는다면 collectorJob1, collectorJob2 내부의 collect는 계속해서 대기하며 값을 구독하려고 하므로 종료되지 않을 것이다.
자식 코루틴이 종료가 되지 않았기 때문에 부모 코루틴인 runTest도 종료가 되지 않고, 그래서 계속해서 테스트가 멈추지 않을 것이다.
다만 틀린 경우에는 assert의 경우 실패하면 AssertionError를 던지기 때문에 Exception이 발생하여 종료되게 된다.
결론은 정답은 달라지지 않지만, 정상적인 종료를 위한 호출코드이다.collectorJob2 생성 윗 부분에서 delay를 주지 않았다면?
emittorJob이 실행되고, collectorJob1이 실행되고 바로 collectorJob2가 실행된다.
collectorJob2도 collectorJob1과 마찬가지로 replay를 통해 1을 수신하고 나머지 값들 또한 차례대로 받게 된다. 11223344가 된다.