{"componentChunkName":"component---src-containers-post-index-tsx","path":"/backend/kotlin-in-action/16장-플로우/","result":{"pageContext":{"next":{"id":"368e41f3-7b7f-530b-b274-9dd74a426734","html":"<h2 id=\"-151-코루틴-스코프가-코루틴-간의-구조를-확립한다\" style=\"position:relative;\"><a href=\"#-151-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%8A%A4%EC%BD%94%ED%94%84%EA%B0%80-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B0%84%EC%9D%98-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%ED%99%95%EB%A6%BD%ED%95%9C%EB%8B%A4\" aria-label=\" 151 코루틴 스코프가 코루틴 간의 구조를 확립한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 15.1 코루틴 스코프가 코루틴 간의 구조를 확립한다</h2>\n<ul>\n<li>구조화된 동시성을 통해 각 코루틴은 코루틴 스코프에 속하게 된다.</li>\n<li>다른 코루틴 빌더의 본문에서 <code>launch</code>나 <code>async</code>를 사용해 새로운 코루틴을 만들면 이 새로운 코루틴은 자동으로 해당 코루틴의 자식이 된다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    runBlocking {\n        launch {\n            delay(1.seconds)\n            launch {\n                delay(250.milliseconds)\n                log(&quot;Grandchild done&quot;)\n            }\n            log(&quot;Child 1 done!&quot;)\n        }\n        launch {\n            delay(500.milliseconds)\n            log(&quot;Child 2 done!&quot;)\n        }\n        log(&quot;Parent done!&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] Parent done!\n524 [main @coroutine#3] Child 2 done!\n1020 [main @coroutine#2] Child 1 done!\n1275 [main @coroutine#4] Grandchild done</code>\n        </deckgo-highlight-code>\n<ul>\n<li>모든 자식 코루틴이 완료될 때까지 프로그램이 종료되지 않는다.</li>\n</ul>\n<h3 id=\"-1511-코루틴-스코프-생성-coroutinescope-함수\" style=\"position:relative;\"><a href=\"#-1511-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%8A%A4%EC%BD%94%ED%94%84-%EC%83%9D%EC%84%B1-coroutinescope-%ED%95%A8%EC%88%98\" aria-label=\" 1511 코루틴 스코프 생성 coroutinescope 함수 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.1.1 코루틴 스코프 생성: coroutineScope 함수</h3>\n<ul>\n<li>코루틴 빌더를 사용해 새로운 코루틴을 만들면 이 코루틴은 자체적인 <code>CoroutineScope</code>를 생성한다.</li>\n<li><code>CoroutineScope</code> 함수의 전형적인 사용 사례는 동시적 작업 분해(여러 코루틴을 활용해 계산 수행)</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">suspend fun generateValue(): Int {\n    delay(500.milliseconds)\n    return Random.nextInt(0, 10)\n}\n\nsuspend fun computeSum() {\n    log(&quot;Computing a sum...&quot;)\n    val sum = coroutineScope {\n        val a = async { generateValue() }\n        val b = async { generateValue() }\n        a.await() + b.await()\n    }\n    log(&quot;Sum is $sum&quot;)\n}</code>\n        </deckgo-highlight-code>\n<h3 id=\"-1512-코루틴-스코프를-컴포넌트와-연관시키기-coroutinescope\" style=\"position:relative;\"><a href=\"#-1512-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%8A%A4%EC%BD%94%ED%94%84%EB%A5%BC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EC%97%B0%EA%B4%80%EC%8B%9C%ED%82%A4%EA%B8%B0-coroutinescope\" aria-label=\" 1512 코루틴 스코프를 컴포넌트와 연관시키기 coroutinescope permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.1.2 코루틴 스코프를 컴포넌트와 연관시키기: CoroutineScope</h3>\n<ul>\n<li><code>coroutineScope</code> 함수가 작업을 분해하는 데 사용되는 반면 구체적 생명주기를 정의하고, 동시 처리나 코루틴의 시작과 종료를 관리하는 클래스를 만들고 싶을 대도 잇다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class ComponentWithScope(dispatcher: CoroutineDispatcher = Dispatchers.Default) {\n    private val scope = CoroutineScope(dispatcher + SupervisorJob())\n\n    fun start() {\n        log(&quot;Starting&quot;)\n        scope.launch {\n            while (true) {\n                delay(500.milliseconds)\n                log(&quot;Component working!&quot;)\n            }\n        }\n        scope.launch {\n            log(&quot;Doing a one-off task...&quot;)\n            delay(500.milliseconds)\n            log(&quot;Task done!&quot;)\n        }\n    }\n\n    fun stop() {\n        log(&quot;Stopping!&quot;)\n        scope.cancel()\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>이 <code>Component</code> 클래스의 인스턴스를 생성하고 <code>start</code>를 호출하면 컴포넌트 내부에서 코루틴이 시작된다.</li>\n</ul>\n<h3 id=\"-1513-globalscope의-위험성\" style=\"position:relative;\"><a href=\"#-1513-globalscope%EC%9D%98-%EC%9C%84%ED%97%98%EC%84%B1\" aria-label=\" 1513 globalscope의 위험성 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.1.3 GlobalScope의 위험성</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    GlobalScope.launch {\n        delay(1000)\n        launch {\n            delay(250)\n            log(&quot;Grandchild done&quot;)\n        }\n        log(&quot;Child 1 done!&quot;)\n    }\n\n    GlobalScope.launch {\n        delay(500)\n        log(&quot;Child 2 done!&quot;)\n    }\n    log(&quot;Parent done!&quot;)\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] Parent done!</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>GlobalScope</code>는 전역 수준에 존재하는 스코프</li>\n<li><code>GlobalScope</code>를 사용하면 구조화된 동시성이 제공하는 모든 이점을 포기</li>\n<li>자동취소 불가, 생명주기 개념 없음</li>\n</ul>\n<h3 id=\"-1514-코루틴-콘텍스트와-구조화된-동시성\" style=\"position:relative;\"><a href=\"#-1514-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%BD%98%ED%85%8D%EC%8A%A4%ED%8A%B8%EC%99%80-%EA%B5%AC%EC%A1%B0%ED%99%94%EB%90%9C-%EB%8F%99%EC%8B%9C%EC%84%B1\" aria-label=\" 1514 코루틴 콘텍스트와 구조화된 동시성 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.1.4 코루틴 콘텍스트와 구조화된 동시성</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    runBlocking(Dispatchers.Default) {\n        log(coroutineContext)\n        launch {\n            log(coroutineContext)\n            launch(Dispatchers.IO + CoroutineName(&quot;mine&quot;)) {\n                log(coroutineContext)\n            }\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [DefaultDispatcher-worker-1 @coroutine#1] [CoroutineId(1), &quot;coroutine#1&quot;:BlockingCoroutine{Active}@3a617b0f, Dispatchers.Default]\n13 [DefaultDispatcher-worker-2 @coroutine#2] [CoroutineId(2), &quot;coroutine#2&quot;:StandaloneCoroutine{Active}@5e2dba97, Dispatchers.Default]\n14 [DefaultDispatcher-worker-3 @mine#3] [CoroutineName(mine), CoroutineId(3), &quot;mine#3&quot;:StandaloneCoroutine{Active}@12764f6a, Dispatchers.IO]</code>\n        </deckgo-highlight-code>\n<ul>\n<li>자식 코루틴은 부모의 콘텍스트 상속</li>\n<li>새로운 코루틴은 부모-자식 관계 설정하는 역할을 하는 새 Job 객체 생성</li>\n<li>디스패처를 지정하지 않고 새로운 코루틴을 시작하면 부모 코루틴의 디스패처에서 실행</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking(CoroutineName(&quot;A&quot;)) {\n    log(&quot;A&#39;s job: ${coroutineContext.job}&quot;)\n    launch(CoroutineName(&quot;B&quot;)) {\n        log(&quot;B&#39;s job: ${coroutineContext.job}&quot;)\n        log(&quot;B&#39;s parent: ${coroutineContext.job.parent}&quot;)\n    }\n    log(&quot;A&#39;s children: ${coroutineContext.job.children.toList()}&quot;)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>코루틴 간의 부모-자식 관계를 확인할 수 있음</li>\n<li>구조화된 동시성에 의해 설정된 이 부모-자식 관계는 취소와도 연관이 있다.</li>\n</ul>\n<h2 id=\"-152-취소\" style=\"position:relative;\"><a href=\"#-152-%EC%B7%A8%EC%86%8C\" aria-label=\" 152 취소 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 15.2 취소</h2>\n<ul>\n<li>취소는 코드가 완료되기 전에 실행을 중단하는 것을 의미</li>\n<li>취소는 불필요한 작업을 막아준다.</li>\n<li>취소는 메모리나 리소스 누수 방지에 도움을 준다.</li>\n<li>취소는 오류 처리에서도 중요한 역할을 한다.</li>\n</ul>\n<h3 id=\"-1521-취소-촉발\" style=\"position:relative;\"><a href=\"#-1521-%EC%B7%A8%EC%86%8C-%EC%B4%89%EB%B0%9C\" aria-label=\" 1521 취소 촉발 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.2.1 취소 촉발</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    val launchedJob = launch {\n        log(&quot;I&#39;m launched!&quot;)\n        delay(1000.milliseconds)\n        log(&quot;I&#39;m done!&quot;)\n    }\n    val asyncDeferred = async {\n        log(&quot;I&#39;m async&quot;)\n        delay(1000.milliseconds)\n        log(&quot;I&#39;m done!&quot;)\n    }\n    delay(200.milliseconds)\n    launchedJob.cancel()\n    asyncDeferred.cancel()\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#2] I&#39;m launched!\n11 [main @coroutine#3] I&#39;m async</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>cancel</code>을 호출해 해당 코루틴의 취소를 촉발할 수 있다.</li>\n</ul>\n<h3 id=\"-1522-시간제한이-초과된-후-자동으로-취소-호출\" style=\"position:relative;\"><a href=\"#-1522-%EC%8B%9C%EA%B0%84%EC%A0%9C%ED%95%9C%EC%9D%B4-%EC%B4%88%EA%B3%BC%EB%90%9C-%ED%9B%84-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EC%B7%A8%EC%86%8C-%ED%98%B8%EC%B6%9C\" aria-label=\" 1522 시간제한이 초과된 후 자동으로 취소 호출 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.2.2 시간제한이 초과된 후 자동으로 취소 호출</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">suspend fun calculateSomething(): Int {\n    delay(3.seconds)\n    return 2 + 2\n}\n\nfun main() = runBlocking {\n    val quickResult = withTimeoutOrNull(500.milliseconds) {\n        calculateSomething()\n    }\n    println(quickResult) // null\n    val slowResult = withTimeoutOrNull(5.seconds) {\n        calculateSomething()\n    }\n    println(slowResult) // 4\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>withTimeout</code>, <code>withTimeoutOrNull</code> 함수는 계산에 쓸 최대 시간을 제한하면서 값을 계산할 수 있게 해준다.</li>\n</ul>\n<h3 id=\"-1523-취소는-모든-자식-코루틴에게-전파된다\" style=\"position:relative;\"><a href=\"#-1523-%EC%B7%A8%EC%86%8C%EB%8A%94-%EB%AA%A8%EB%93%A0-%EC%9E%90%EC%8B%9D-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%97%90%EA%B2%8C-%EC%A0%84%ED%8C%8C%EB%90%9C%EB%8B%A4\" aria-label=\" 1523 취소는 모든 자식 코루틴에게 전파된다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.2.3 취소는 모든 자식 코루틴에게 전파된다</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    val job = launch {\n        launch {\n            launch {\n                launch {\n                    log(&quot;I&#39;m started&quot;)\n                    delay(500.milliseconds)\n                    log(&quot;I&#39;m done!&quot;)\n                }\n            }\n        }\n    }\n    delay(200.milliseconds)\n    job.cancel()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>코루틴을 취소하면 해당 코루틴의 모든 자식 코루틴도 자동으로 취소된다.</li>\n<li>여러 계층에 걸쳐 코루틴이 중첩돼 있는 경우에도 가장 바깥쪽 코루틴을 취소하면 고손자 코루틴까지 모두 적절히 취소된다.</li>\n</ul>\n<h3 id=\"-1524-취소된-코루틴은-특별한-지점에서-cancellationexception을-던진다\" style=\"position:relative;\"><a href=\"#-1524-%EC%B7%A8%EC%86%8C%EB%90%9C-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%9D%80-%ED%8A%B9%EB%B3%84%ED%95%9C-%EC%A7%80%EC%A0%90%EC%97%90%EC%84%9C-cancellationexception%EC%9D%84-%EB%8D%98%EC%A7%84%EB%8B%A4\" aria-label=\" 1524 취소된 코루틴은 특별한 지점에서 cancellationexception을 던진다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.2.4 취소된 코루틴은 특별한 지점에서 CancellationException을 던진다</h3>\n<ul>\n<li>취소 메커니즘은 <code>CancellationException</code>이라는 특수한 예외를 특별한 지점에서 던지는 방식으로 작동</li>\n<li>취소된 코루틴은 일시 중단 지점에서 <code>CancellationException</code>을 던진다.</li>\n<li>코루틴 계층에서 취소를 전파하기 때문에 이 예외를 직접 처리하지 않아야 한다.</li>\n</ul>\n<h3 id=\"-1525-취소는-협력적이다\" style=\"position:relative;\"><a href=\"#-1525-%EC%B7%A8%EC%86%8C%EB%8A%94-%ED%98%91%EB%A0%A5%EC%A0%81%EC%9D%B4%EB%8B%A4\" aria-label=\" 1525 취소는 협력적이다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.2.5 취소는 협력적이다</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">suspend fun doCpuHeavyWork(): Int {\n    log(&quot;I&#39;m doing work!&quot;)\n    var counter = 0\n    val startTime = System.currentTimeMillis()\n    while (System.currentTimeMillis() &lt; startTime + 500) {\n        counter++\n    }\n    return counter\n}\n\nfun main() = runBlocking {\n    val myJob = launch {\n        repeat(5) {\n            doCpuHeavyWork()\n        }\n    }\n    delay(600.milliseconds)\n    myJob.cancel()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>프로그램이 종료되기 전에 <code>doCpuHeavyWork</code>가 5번 완료된다.</li>\n<li><code>doCpuHeavyWork</code> 함수는 일시 중단 지점을 포함하지 않는다.</li>\n<li>코틀린 코루틴의 취소가 협력적인 이유는 결국 스스로 취소 가능하게 로직을 제공해야 하기 때문\n<ul>\n<li><code>delay</code> 호출을 추가하면 됨.</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"-1526-코루틴이-취소됐는지-확인\" style=\"position:relative;\"><a href=\"#-1526-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%9D%B4-%EC%B7%A8%EC%86%8C%EB%90%90%EB%8A%94%EC%A7%80-%ED%99%95%EC%9D%B8\" aria-label=\" 1526 코루틴이 취소됐는지 확인 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.2.6 코루틴이 취소됐는지 확인</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">val myJob = launch {\n    repeat(5) {\n        doCpuHeavyWork()\n        if (!isActive) return@launch\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>코루틴이 취소됐는지 확인할 때는 <code>isActive</code> 속성을 확인\n<ul>\n<li><code>false</code>이면 비활성화</li>\n</ul>\n</li>\n<li><code>ensureActive</code> 함수는 비활성화일 때, <code>CancellationException</code>을 던진다.</li>\n</ul>\n<h3 id=\"-1527-다른-코루틴에게-기회를-주기-yield-함수\" style=\"position:relative;\"><a href=\"#-1527-%EB%8B%A4%EB%A5%B8-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%97%90%EA%B2%8C-%EA%B8%B0%ED%9A%8C%EB%A5%BC-%EC%A3%BC%EA%B8%B0-yield-%ED%95%A8%EC%88%98\" aria-label=\" 1527 다른 코루틴에게 기회를 주기 yield 함수 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.2.7 다른 코루틴에게 기회를 주기: yield 함수</h3>\n<ul>\n<li>코루틴 라이브러리는 <code>yield</code> 함수 제공\n<ul>\n<li>취소 가능 지점 제공</li>\n<li>점유된 디스패처에서 다른 코루틴이 작업할 수 있게 해줌</li>\n</ul>\n</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun doCpuHeavyWork(): Int {\n    var counter = 0\n    val startTime = System.currentTimeMillis()\n    while (System.currentTimeMillis() &lt; startTime + 500) {\n        counter++\n    }\n    return counter\n}\n\nfun main() {\n    runBlocking {\n        launch {\n            repeat(3) {\n                doCpuHeavyWork()\n            }\n        }\n        launch {\n            repeat(3) {\n                doCpuHeavyWork()\n            }\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>첫 번째 코루틴이 완료될 때까지 두 번째 코루틴은 실행되지 않음.</li>\n<li>일시 중단 지점이 없기 때문</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">suspend fun doCpuHeavyWork(): Int {\n    var counter = 0\n    val startTime = System.currentTimeMillis()\n    while (System.currentTimeMillis() &lt; startTime + 500) {\n        counter++\n        yield()\n    }\n    return counter\n}\n\nfun main() {\n    runBlocking {\n        launch {\n            repeat(3) {\n                doCpuHeavyWork()\n            }\n        }\n        launch {\n            repeat(3) {\n                doCpuHeavyWork()\n            }\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>yield</code> 함수를 사용하면 코루틴이 교차 실행됨</li>\n</ul>\n<h3 id=\"-1528-리소스를-얻을-때-취소를-염두에-두기\" style=\"position:relative;\"><a href=\"#-1528-%EB%A6%AC%EC%86%8C%EC%8A%A4%EB%A5%BC-%EC%96%BB%EC%9D%84-%EB%95%8C-%EC%B7%A8%EC%86%8C%EB%A5%BC-%EC%97%BC%EB%91%90%EC%97%90-%EB%91%90%EA%B8%B0\" aria-label=\" 1528 리소스를 얻을 때 취소를 염두에 두기 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.2.8 리소스를 얻을 때 취소를 염두에 두기</h3>\n<ul>\n<li>취소 후, <code>close</code> 함수가 호출되지 않고 리소스가 누수될 수 있음.</li>\n<li><code>finally</code> 블록을 사용해 명시적으로 닫자.</li>\n<li>리소스가 <code>AutoClosable</code> 인터페이스를 구현하는 경우 <code>.use</code> 함수를 사용해 같은 동작을 더 간결하게 처리할 수 있다.</li>\n</ul>\n<h3 id=\"-1529-프레임워크가-여러분-대신-취소를-할-수-있다\" style=\"position:relative;\"><a href=\"#-1529-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC%EA%B0%80-%EC%97%AC%EB%9F%AC%EB%B6%84-%EB%8C%80%EC%8B%A0-%EC%B7%A8%EC%86%8C%EB%A5%BC-%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8B%A4\" aria-label=\" 1529 프레임워크가 여러분 대신 취소를 할 수 있다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 15.2.9 프레임워크가 여러분 대신 취소를 할 수 있다</h3>\n<ul>\n<li>많은 실제 어플리케이션에서는 프레임워크가 코루틴 스코프를 제공하고 자동취소한다.</li>\n<li>사용자는 적절한 코루틴 스코프를 선택</li>\n</ul>","excerpt":"📖 15.1 코루틴 스코프가 코루틴 간의 구조를 확립한다 구조화된 동시성을 통해 각 코루틴은 코루틴 스코프에 속하게 된다. 다른 코루틴 빌더의 본문에서 launch나 async를 사용해 새로운 코루틴을 만들면 이 새로운 코루틴은 자동으로 해당 코루틴의 자식이 된다. 모든 자식 코루틴이 완료될 때까지 프로그램이 종료되지 않는다. 🔖 15.1.1 코루틴 스코프 생성: coroutineScope 함수 코루틴 빌더를 사용해 새로운 코루틴을 만들면 이 코루틴은 자체적인 CoroutineScope…","fields":{"slug":"/backend/kotlin-in-action/15장-구조화된_동시성/"},"frontmatter":{"title":"Kotlin in Action - 15장 구조화된 동시성","thumbnail":{"childImageSharp":{"fluid":{"src":"/static/f7f7ddfa31d1614405dc5af1487b9ec4/9c94d/kotlin-in-action.png"}}},"draft":false,"category":"Back-End","tags":["Kotlin"],"date":"June 15, 2025"}},"previous":{"id":"21a17e28-41fd-530f-90e8-1c0769e41d34","html":"<h2 id=\"-171-플로우-연산자로-플로우-조작\" style=\"position:relative;\"><a href=\"#-171-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%97%B0%EC%82%B0%EC%9E%90%EB%A1%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%A1%B0%EC%9E%91\" aria-label=\" 171 플로우 연산자로 플로우 조작 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 17.1 플로우 연산자로 플로우 조작</h2>\n<ul>\n<li>시퀀스와 마찬가지로 플로우도 중간 연산자와 최종 연산자를 구분</li>\n<li>중간 연산자는 코드를 실행하지 않고 변경된 플로우를 반환하며, 최종 연산자는 컬렉션, 개별 원소, 계산된 값을 반환하거나 아무 값도 반환하지 않으면서 플로우를 수집하고 실제 코드를 실행한다.</li>\n</ul>\n<h2 id=\"-172-중간-연산자는-업스트림-플로우에-적용되고-다운스트림-플로우를-반환한다\" style=\"position:relative;\"><a href=\"#-172-%EC%A4%91%EA%B0%84-%EC%97%B0%EC%82%B0%EC%9E%90%EB%8A%94-%EC%97%85%EC%8A%A4%ED%8A%B8%EB%A6%BC-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%97%90-%EC%A0%81%EC%9A%A9%EB%90%98%EA%B3%A0-%EB%8B%A4%EC%9A%B4%EC%8A%A4%ED%8A%B8%EB%A6%BC-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EB%B0%98%ED%99%98%ED%95%9C%EB%8B%A4\" aria-label=\" 172 중간 연산자는 업스트림 플로우에 적용되고 다운스트림 플로우를 반환한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 17.2 중간 연산자는 업스트림 플로우에 적용되고 다운스트림 플로우를 반환한다</h2>\n<ul>\n<li>중간 연산자는 플로우에 적용돼 새로운 플로우를 반환</li>\n<li>업스트림 플로우\n<ul>\n<li>연산자가 적용되는 플로우</li>\n</ul>\n</li>\n<li>다운스트림 플로우\n<ul>\n<li>중간 연산자가 반환하는 플로우</li>\n<li>또 다른 연산자의 업스트림 플로우로 작용할 수 있음.</li>\n</ul>\n</li>\n<li>시퀀스와 마찬가지로 중간 연산자가 호출되더라도 플로우 코드가 실제로 실행되지는 않는다.\n<ul>\n<li>콜드 상태</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"-1721-업스트림-원소별로-임의의-값을-배출-transform-함수\" style=\"position:relative;\"><a href=\"#-1721-%EC%97%85%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%9B%90%EC%86%8C%EB%B3%84%EB%A1%9C-%EC%9E%84%EC%9D%98%EC%9D%98-%EA%B0%92%EC%9D%84-%EB%B0%B0%EC%B6%9C-transform-%ED%95%A8%EC%88%98\" aria-label=\" 1721 업스트림 원소별로 임의의 값을 배출 transform 함수 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.1 업스트림 원소별로 임의의 값을 배출: transform 함수</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val names = flow {\n        emit(&quot;Jo&quot;)\n        emit(&quot;May&quot;)\n        emit(&quot;Sue&quot;)\n    }\n    val uppercasedNames = names.map {\n        it.uppercase()\n    }\n    runBlocking {\n        uppercasedNames.collect { println(&quot;$it &quot;) }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>map</code> 함수는 업스트림 플로우를 받아 원소를 변환한 후 다운스트림 플로우에 원소를 배출 할 수 있다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val names = flow {\n        emit(&quot;Jo&quot;)\n        emit(&quot;May&quot;)\n        emit(&quot;Sue&quot;)\n    }\n    val upperAndLowercasedNames = names.transform {\n        emit(it.uppercase())\n        emit(it.lowercase())\n    }\n    runBlocking {\n        upperAndLowercasedNames.collect { println(&quot;$it &quot;) }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>하나 이상의 원소를 배출하고 싶을 때는 <code>transform</code> 함수 사용</li>\n<li><code>flowOf</code> 함수로 줄임표현 사용 가능</li>\n</ul>\n<h3 id=\"-1722-take나-관련-연산자는-플로우를-취소할-수-있다\" style=\"position:relative;\"><a href=\"#-1722-take%EB%82%98-%EA%B4%80%EB%A0%A8-%EC%97%B0%EC%82%B0%EC%9E%90%EB%8A%94-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%B7%A8%EC%86%8C%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8B%A4\" aria-label=\" 1722 take나 관련 연산자는 플로우를 취소할 수 있다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.2 take나 관련 연산자는 플로우를 취소할 수 있다</h3>\n<ul>\n<li><code>takeWhile</code> 같은 함수들을 플로우에서도 똑같이 쓸 수 있다.</li>\n<li>이런 연산자를 사용하면 연산자가 지정한 조건이 더 이상 유효하지 않을 때 업스트림 플로우가 취소되며, 더 이상 원소가 배출되지 않는다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val temps = getTemperatures()\n    temps\n        .take(5)\n        .collect {\n            log(it)\n        }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>take</code> 함수는 수집자와 관련된 코루틴 스코프를 취소하는 방식 외에 플로우 수집을 제어된 방식으로 취소하는 또 다른 방법</li>\n</ul>\n<h3 id=\"-1723-플로우의-각-단계-후킹-onstart-oneach-oncompletion-onempty\" style=\"position:relative;\"><a href=\"#-1723-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%9D%98-%EA%B0%81-%EB%8B%A8%EA%B3%84-%ED%9B%84%ED%82%B9-onstart-oneach-oncompletion-onempty\" aria-label=\" 1723 플로우의 각 단계 후킹 onstart oneach oncompletion onempty permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.3 플로우의 각 단계 후킹: onStart, onEach, onCompletion, onEmpty</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val temps = getTemperatures()\n    temps\n        .take(5)\n        .onCompletion { cause -&gt;\n            if (cause != null) {\n                println(&quot;An error occurred! $cause&quot;)\n            } else {\n                println(&quot;Completed!&quot;)\n            }\n        }\n        .collect {\n            println(it)\n        }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>원소 수집 후 종료되는 것을 확인하기 위해 <code>onCompletion</code> 연산자를 사용할 수 있다.\n<ul>\n<li>플로우가 정상 종료되거나, 취소되거나, 예외로 종료된 후에 호출되는 람다를 지정할 수 있게 해준다.</li>\n</ul>\n</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    flow\n        .onEmpty {\n            println(&quot;Nothing - emitting default value!&quot;)\n            emit(0)\n        }\n        .onStart {\n            println(&quot;Starting&quot;)\n        }\n        .onEach {\n            println(&quot;On $it&quot;)\n        }\n        .onCompletion {\n            println(&quot;Done!&quot;)\n        }\n        .collect()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>onCompletion</code>은 플로우 생명주기의 특정 단계에서 작업을 수행할 수 있는 중간 연산자</li>\n<li><code>onStart</code>는 플로우의 수집이 시작될 때 첫 번째 배출이 일어나기 전에 실행</li>\n<li><code>onEach</code>는 업스트림 플로우에서 배출된 각 원소에 대해 작업을 수행한 후 다운스트림 플로우에 전달</li>\n<li><code>onEmpty</code>는 로직을 추가로 수행하거나 기본값을 제공</li>\n</ul>\n<h3 id=\"-1724-다운스트림-연산자와-수집자를-위한-원소-버퍼링-buffer-연산자\" style=\"position:relative;\"><a href=\"#-1724-%EB%8B%A4%EC%9A%B4%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%97%B0%EC%82%B0%EC%9E%90%EC%99%80-%EC%88%98%EC%A7%91%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%9B%90%EC%86%8C-%EB%B2%84%ED%8D%BC%EB%A7%81-buffer-%EC%97%B0%EC%82%B0%EC%9E%90\" aria-label=\" 1724 다운스트림 연산자와 수집자를 위한 원소 버퍼링 buffer 연산자 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.4 다운스트림 연산자와 수집자를 위한 원소 버퍼링: buffer 연산자</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun getAllUserIds(): Flow&lt;Int&gt; {\n    return flow {\n        repeat(3) {\n            delay(200.milliseconds)\n            log(&quot;Emitting!&quot;)\n            emit(it)\n        }\n    }\n}\n\nsuspend fun getProfileFromNetwork(id: Int): String {\n    delay(2.seconds)\n    return &quot;Profile[$id]&quot;\n}\n\nfun main() {\n  val ids = getAllUserIds()\n  runBlocking {\n    ids\n      .map { getProfileFromNetwork(it) }\n      .collect { log(&quot;Got $it&quot;) }\n  }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>느린 데이터베이스에 접근해 사용자 식별자의 플로우를 얻어오는 과정</li>\n<li>값 생산자는 수집자가 이전 원소를 처리할 때까지 작업을 중단</li>\n</ul>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] Emitting!\n2028 [main @coroutine#1] Got Profile[0]\n2234 [main @coroutine#1] Emitting!\n4240 [main @coroutine#1] Got Profile[1]\n4447 [main @coroutine#1] Emitting!\n6453 [main @coroutine#1] Got Profile[2]</code>\n        </deckgo-highlight-code>\n<ul>\n<li>ID의 배출과 프로필 요청이 뒤섞여 있다.</li>\n<li>원소가 배출되면 다운스트림 플로우가 해당 원소를 처리할 때까지 생산자 코드는 계속되지 않는다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val ids = getAllUserIds()\n    runBlocking {\n        ids\n            .buffer(3)\n            .map { getProfileFromNetwork(it) }\n            .collect { log(&quot;Got $it&quot;) }\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#2] Emitting!\n213 [main @coroutine#2] Emitting!\n414 [main @coroutine#2] Emitting!\n2030 [main @coroutine#1] Got Profile[0]\n4040 [main @coroutine#1] Got Profile[1]\n6046 [main @coroutine#1] Got Profile[2]</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>buffer</code> 연산자는 버퍼를 추가해서 다운스트림 플로우가 이미 배출된 원소를 처리하느라 바쁜 동안에도 업스트림 플로우가 원소를 배출할 수 있게 해준다.</li>\n<li>버퍼를 추가했을 때 실행 시간이 줄어들었다.</li>\n<li><code>onBufferOverflow</code> 파라미터를 통해 버퍼 용량이 초과될 때 어떤 일이 발생할지 지정할 수 있다.\n<ul>\n<li>생산자 대기(SUSPEND)</li>\n<li>오래된 값 버리기(DROP_OLDEST)</li>\n<li>추가 중인 마지막 값을 버릴지(DROP_LATEST)</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"-1725-중간값을-버리는-연산자-conflate-연산자\" style=\"position:relative;\"><a href=\"#-1725-%EC%A4%91%EA%B0%84%EA%B0%92%EC%9D%84-%EB%B2%84%EB%A6%AC%EB%8A%94-%EC%97%B0%EC%82%B0%EC%9E%90-conflate-%EC%97%B0%EC%82%B0%EC%9E%90\" aria-label=\" 1725 중간값을 버리는 연산자 conflate 연산자 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.5 중간값을 버리는 연산자: conflate 연산자</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    runBlocking {\n        val temps = getTemperatures()\n        temps\n            .onEach {\n                log(&quot;Read $it from sensor&quot;)\n            }\n            .conflate()\n            .collect { \n                log(&quot;Collected $it&quot;)\n                delay(1.seconds)\n            }\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">142709 [main @coroutine#1] Collected 6\n142864 [main @coroutine#2] Read 28 from sensor\n143370 [main @coroutine#2] Read 24 from sensor\n143711 [main @coroutine#1] Collected 24\n143876 [main @coroutine#2] Read -2 from sensor\n144382 [main @coroutine#2] Read 1 from sensor\n144715 [main @coroutine#1] Collected 1\n144888 [main @coroutine#2] Read 1 from sensor\n145391 [main @coroutine#2] Read 29 from sensor\n145719 [main @coroutine#1] Collected 29\n145893 [main @coroutine#2] Read 9 from sensor\n146396 [main @coroutine#2] Read 1 from sensor\n146725 [main @coroutine#1] Collected 1</code>\n        </deckgo-highlight-code>\n<ul>\n<li>값 생산자가 방해받지 않고 작업을 계속할 수 있게 하는 또 다른 방법은 수집자가 바쁜동안 배출된 항목을 그냥 버리는 것</li>\n<li>다운스트림의 수집자에서는 중간 원소가 버려진다.</li>\n<li><code>conflate</code>를 쓰면 업스트림 플로우의 실행을 다운스트림 연산자의 실행과 분리할 수 있다.</li>\n<li>느린 수집자가 플로우에서 최신 원소만 처리하게 함으로써 성능을 유지할 수 있다.</li>\n</ul>\n<h3 id=\"-1726-일정-시간-동안-값을-필터링하는-연산자-debounce-연산자\" style=\"position:relative;\"><a href=\"#-1726-%EC%9D%BC%EC%A0%95-%EC%8B%9C%EA%B0%84-%EB%8F%99%EC%95%88-%EA%B0%92%EC%9D%84-%ED%95%84%ED%84%B0%EB%A7%81%ED%95%98%EB%8A%94-%EC%97%B0%EC%82%B0%EC%9E%90-debounce-%EC%97%B0%EC%82%B0%EC%9E%90\" aria-label=\" 1726 일정 시간 동안 값을 필터링하는 연산자 debounce 연산자 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.6 일정 시간 동안 값을 필터링하는 연산자: debounce 연산자</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">val searchQuery = flow {\n    emit(&quot;K&quot;)\n    delay(100.milliseconds)\n    emit(&quot;Ko&quot;)\n    delay(200.milliseconds)\n    emit(&quot;Kotl&quot;)\n    delay(500.milliseconds)\n    emit(&quot;Kotlin&quot;)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>사용자가 검색 질의 문자열을 타이핑하는 것을 시뮬레이션</li>\n<li>일정시간동안 입력이 없으면 검색 결과 표시</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n  searchQuery\n    .debounce(250.milliseconds)\n    .collect {\n      log(&quot;Searching for $it&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] Searching for Kotl\n247 [main @coroutine#1] Searching for Kotlin</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>debounce</code> 연산자는 업스트림에서 원소가 배출되지 않은 상태로 정해진 타임아웃 시간이 지나야만 항목을 다운스트림 플로우로 배출</li>\n</ul>\n<h3 id=\"-1727-플로우가-실행되는-코루틴-콘텍스트를-바꾸기-flowon-연산자\" style=\"position:relative;\"><a href=\"#-1727-%ED%94%8C%EB%A1%9C%EC%9A%B0%EA%B0%80-%EC%8B%A4%ED%96%89%EB%90%98%EB%8A%94-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%BD%98%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EB%B0%94%EA%BE%B8%EA%B8%B0-flowon-%EC%97%B0%EC%82%B0%EC%9E%90\" aria-label=\" 1727 플로우가 실행되는 코루틴 콘텍스트를 바꾸기 flowon 연산자 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.7 플로우가 실행되는 코루틴 콘텍스트를 바꾸기: flowOn 연산자</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    runBlocking {\n        flowOf(1)\n            .onEach { log(&quot;A&quot;) }\n            .flowOn(Dispatchers.Default)\n            .onEach { log(&quot;B&quot;) }\n            .flowOn(Dispatchers.IO)\n            .onEach { log(&quot;C&quot;) }\n            .collect()\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [DefaultDispatcher-worker-3 @coroutine#3] A\n24 [DefaultDispatcher-worker-1 @coroutine#2] B\n26 [main @coroutine#1] C</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>flowOn</code> 연산자는 처리 파이프라인의 일부를 다른 디스패처나 다른 코루틴 콘텍스트에서 힐행할 수 있다.\n<ul>\n<li>코루틴 콘텍스트를 조정</li>\n</ul>\n</li>\n<li><code>flowOn</code> 연산자는 업스트림 플로우의 디스패처에만 영향을 미친다.</li>\n</ul>\n<h2 id=\"-173-커스텀-중간-연산자-만들기\" style=\"position:relative;\"><a href=\"#-173-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%A4%91%EA%B0%84-%EC%97%B0%EC%82%B0%EC%9E%90-%EB%A7%8C%EB%93%A4%EA%B8%B0\" aria-label=\" 173 커스텀 중간 연산자 만들기 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 17.3 커스텀 중간 연산자 만들기</h2>\n<ul>\n<li>일반적으로 중간 연산자는 동시에 수집자와 생산자의 역할을 한다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun Flow&lt;Double&gt;.averageOfLast(n: Int): Flow&lt;Double&gt; =\n    flow {\n        val numbers = mutableListOf&lt;Double&gt;()\n        collect { \n            if (numbers.size &gt; n) {\n                numbers.removeFirst()\n            }\n            numbers.add(it)\n            emit(numbers.average())\n        }\n    }\n\nfun main() = runBlocking {\n  flowOf(1.0, 2.0, 30.0, 121.0)\n    .averageOfLast(3)\n    .collect {\n      print(&quot;$it &quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>내부적으로 발생한 숫자의 리스트를 유지하면서 만난 값들의 평균을 배출할 수 있다.</li>\n<li>최적화는 밖에 드러나지 않고 코드의 행동방식에는 영향을 미치지 않는다.</li>\n</ul>\n<h2 id=\"-174-최종-연산자는-업스트림-플로우를-실행하고-값을-계산한다\" style=\"position:relative;\"><a href=\"#-174-%EC%B5%9C%EC%A2%85-%EC%97%B0%EC%82%B0%EC%9E%90%EB%8A%94-%EC%97%85%EC%8A%A4%ED%8A%B8%EB%A6%BC-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%8B%A4%ED%96%89%ED%95%98%EA%B3%A0-%EA%B0%92%EC%9D%84-%EA%B3%84%EC%82%B0%ED%95%9C%EB%8B%A4\" aria-label=\" 174 최종 연산자는 업스트림 플로우를 실행하고 값을 계산한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 17.4 최종 연산자는 업스트림 플로우를 실행하고 값을 계산한다</h2>\n<ul>\n<li>최종 연산자는 단일 값이나 값의 컬렉션을 계산하거나, 플로우의 실행을 촉발시켜 지정된 연산과 부수 효과를 수행한다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    getTemperatures()\n        .onEach { \n            log(it)\n        }\n        .collect()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>collect</code>는 플로우의 각 원소에 대해 실행할 람다를 지정할 수 있는 유용한 지름길을 제공</li>\n<li>최종 연산자는 업스트림 플로우의 실행을 담당하기 때문에 항상 일시 중단 함수다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    getTemperatures()\n        .first()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>first</code>나 <code>firstOrNull</code> 같은 최종연산자는 원소를 받은 다음에 업스트림 플로우를 취소할 수 있다.</li>\n</ul>\n<h3 id=\"-1741-프레임워크는-커스텀-연산자를-제공한다\" style=\"position:relative;\"><a href=\"#-1741-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC%EB%8A%94-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%97%B0%EC%82%B0%EC%9E%90%EB%A5%BC-%EC%A0%9C%EA%B3%B5%ED%95%9C%EB%8B%A4\" aria-label=\" 1741 프레임워크는 커스텀 연산자를 제공한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.4.1 프레임워크는 커스텀 연산자를 제공한다</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">@Composable fun TemperatureDisplay(temps: Flow&lt;Int&gt;) {\n    val temperature = temps.collectAsState(null)\n    Box {\n        temperature.value?.let {\n            Text(&quot;The current temperature is $it!&quot;)\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>텍스트를 상자 안에 넣는 간단한 코드</li>\n<li>플로우는 코루틴 기반 툴킷에 강력한 추가 기능을 제공</li>\n<li>코틀린 생태계의 어떤 프레임워크들은 플로우와 직접적인 통합을 제공하며, 커스텀 연산자와 변환 함수도 노출</li>\n</ul>","excerpt":"📖 17.1 플로우 연산자로 플로우 조작 시퀀스와 마찬가지로 플로우도 중간 연산자와 최종 연산자를 구분 중간 연산자는 코드를 실행하지 않고 변경된 플로우를 반환하며, 최종 연산자는 컬렉션, 개별 원소, 계산된 값을 반환하거나 아무 값도 반환하지 않으면서 플로우를 수집하고 실제 코드를 실행한다. 📖 17.…","fields":{"slug":"/backend/kotlin-in-action/17장-플로우_연산자/"},"frontmatter":{"title":"Kotlin in Action - 17장 플로우 연산자","thumbnail":{"childImageSharp":{"fluid":{"src":"/static/f7f7ddfa31d1614405dc5af1487b9ec4/9c94d/kotlin-in-action.png"}}},"draft":false,"category":"Back-End","tags":["Kotlin"],"date":"June 29, 2025"}},"node":{"id":"4cf91309-fe0d-5841-9f3f-1b78e4544680","html":"<h2 id=\"-161-플로우는-연속적인-값의-스트림을-모델링한다\" style=\"position:relative;\"><a href=\"#-161-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%8A%94-%EC%97%B0%EC%86%8D%EC%A0%81%EC%9D%B8-%EA%B0%92%EC%9D%98-%EC%8A%A4%ED%8A%B8%EB%A6%BC%EC%9D%84-%EB%AA%A8%EB%8D%B8%EB%A7%81%ED%95%9C%EB%8B%A4\" aria-label=\" 161 플로우는 연속적인 값의 스트림을 모델링한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 16.1 플로우는 연속적인 값의 스트림을 모델링한다</h2>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">suspend fun createValues(): List&lt;Int&gt; {\n    return buildList {\n        add(1)\n        delay(1.seconds)\n        add(2)\n        delay(1.seconds)\n        add(3)\n        delay(1.seconds)\n    }\n}\n\nfun main() = runBlocking {\n    val list = createValues()\n    list.forEach {\n        log(it)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>모든 값이 계산 된 후 함수가 값을 반환함.</li>\n<li>함수가 실행을 마칠 때까지 기다리지 않고 값을 사용할 수 있도록 비동기적으로 반환하고 싶을 때 플롱우가 유용하다.</li>\n<li>플로우는 시간이 지남에 따라 나타나는 값과 작업할 수 있게 해주는 코루틴 기반의 추상화다</li>\n</ul>\n<h3 id=\"-1611-플로우를-사용하면-배출되자마자-원소를-처리할-수-있다\" style=\"position:relative;\"><a href=\"#-1611-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B4-%EB%B0%B0%EC%B6%9C%EB%90%98%EC%9E%90%EB%A7%88%EC%9E%90-%EC%9B%90%EC%86%8C%EB%A5%BC-%EC%B2%98%EB%A6%AC%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8B%A4\" aria-label=\" 1611 플로우를 사용하면 배출되자마자 원소를 처리할 수 있다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.1.1 플로우를 사용하면 배출되자마자 원소를 처리할 수 있다</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun createValues(): Flow&lt;Int&gt; {\n    return flow {\n        emit(1)\n        delay(1.seconds)\n        emit(2)\n        delay(1.seconds)\n        emit(3)\n        delay(1.seconds)\n    }\n}\n\nfun main() = runBlocking {\n    val myFlowOfValues = createValues()\n    myFlowOfValues.collect { log(it) }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] 1\n1036 [main @coroutine#1] 2\n2042 [main @coroutine#1] 3</code>\n        </deckgo-highlight-code>\n<ul>\n<li>원소가 배출되는 즉시 표시된다.</li>\n</ul>\n<h3 id=\"-1612-코틀린-플로우의-여러-유형\" style=\"position:relative;\"><a href=\"#-1612-%EC%BD%94%ED%8B%80%EB%A6%B0-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%9D%98-%EC%97%AC%EB%9F%AC-%EC%9C%A0%ED%98%95\" aria-label=\" 1612 코틀린 플로우의 여러 유형 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.1.2 코틀린 플로우의 여러 유형</h3>\n<ul>\n<li>콜드 플로우\n<ul>\n<li>비동기 데이터 스트림</li>\n<li>값이 실제로 소비되기 시작할 때만 값을 배출</li>\n</ul>\n</li>\n<li>핫 플로우\n<ul>\n<li>브로드캐스트 방식</li>\n<li>값이 실제로 소비되고 있는지와 상관없이 독립적으로 배출</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"-162-콜드-플로우\" style=\"position:relative;\"><a href=\"#-162-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0\" aria-label=\" 162 콜드 플로우 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 16.2 콜드 플로우</h2>\n<h3 id=\"-1621-flow-빌더-함수를-사용해-콜드-플로우-생성\" style=\"position:relative;\"><a href=\"#-1621-flow-%EB%B9%8C%EB%8D%94-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%83%9D%EC%84%B1\" aria-label=\" 1621 flow 빌더 함수를 사용해 콜드 플로우 생성 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.2.1 flow 빌더 함수를 사용해 콜드 플로우 생성</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    val letters = flow {\n        log(&quot;Emitting A!&quot;)\n        emit(&quot;A&quot;)\n        delay(200.milliseconds)\n        log(&quot;Emitting B!&quot;)\n        emit(&quot;B&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>아무런 출력도 나타나지 않는다.</li>\n<li><code>flow</code> 빌더 함수를 호출해도 실제 작업이 시작되지 않음.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">val counterFlow = flow {\n    var x = 0\n    while (true) {\n        emit(x++)\n        delay(200.milliseconds)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>이 루프는 실제로 플로우가 수집될 때만 실행</li>\n</ul>\n<h3 id=\"-1622-콜드-플로우는-수집되기-전까지-작업을-수행하지-않는다\" style=\"position:relative;\"><a href=\"#-1622-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%8A%94-%EC%88%98%EC%A7%91%EB%90%98%EA%B8%B0-%EC%A0%84%EA%B9%8C%EC%A7%80-%EC%9E%91%EC%97%85%EC%9D%84-%EC%88%98%ED%96%89%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4\" aria-label=\" 1622 콜드 플로우는 수집되기 전까지 작업을 수행하지 않는다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.2.2 콜드 플로우는 수집되기 전까지 작업을 수행하지 않는다</h3>\n<ul>\n<li><code>Flow</code>에 대해 <code>collect</code> 함수를 호출하면 그 로직이 실행된다</li>\n<li>플로우를 수집할 때는 플로우 내부의 일시 중단 코드를 실행하므로 <code>collect</code>는 일시 중단 함수이며, 플로우가 끝날 때까지 일시 중단된다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">val letters = flow {\n    log(&quot;Emitting A!&quot;)\n    emit(&quot;A&quot;)\n    delay(200.milliseconds)\n    log(&quot;Emitting B!&quot;)\n    emit(&quot;B&quot;)\n}\n\nfun main() = runBlocking {\n    letters.collect {\n        log(&quot;Collecting: $it&quot;)\n        delay(500.milliseconds)\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] Emitting A!\n15 [main @coroutine#1] Collecting: A\n737 [main @coroutine#1] Emitting B!\n737 [main @coroutine#1] Collecting: B</code>\n        </deckgo-highlight-code>\n<ul>\n<li>수집자가 플로우의 로직을 실행하는 책임이 있다.</li>\n<li>원소 A, B 사이의 지연시간은 700밀리초이다.\n<ul>\n<li>수집자가 플로우 빌더에 정의된 로직의 실행을 촉발해서 첫 번째 배출 발생</li>\n<li>수집자가 연결된 람다가 호출되면서 메시지를 기록하고 500밀리초 동안 지연</li>\n<li>플로우 람다가 계속 실행되며 200밀리초 동안 추가 지연과 배출이 발생</li>\n</ul>\n</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    letters.collect {\n        log(&quot;(1) Collecting: $it&quot;)\n        delay(500.milliseconds)\n    }\n    letters.collect {\n        log(&quot;(2) Collecting: $it&quot;)\n        delay(500.milliseconds)\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] Emitting A!\n16 [main @coroutine#1] (1) Collecting: A\n743 [main @coroutine#1] Emitting B!\n743 [main @coroutine#1] (1) Collecting: B\n1247 [main @coroutine#1] Emitting A!\n1247 [main @coroutine#1] (2) Collecting: A\n1958 [main @coroutine#1] Emitting B!\n1958 [main @coroutine#1] (2) Collecting: B</code>\n        </deckgo-highlight-code>\n<ul>\n<li>같은 플로우를 여러 번 수집할 수 있다.</li>\n</ul>\n<h3 id=\"-1623-플로우-수집-취소\" style=\"position:relative;\"><a href=\"#-1623-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%88%98%EC%A7%91-%EC%B7%A8%EC%86%8C\" aria-label=\" 1623 플로우 수집 취소 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.2.3 플로우 수집 취소</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    val collector = launch {\n        counterFlow.collect {\n            println(it)\n        }\n    }\n    delay(5.seconds)\n    collector.cancel()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>수집자의 코루틴을 취소하면 다음 취소 지점에서 플로우의 수집이 중단된다.</li>\n</ul>\n<h3 id=\"-1624-콜드-플로우의-내부-구현\" style=\"position:relative;\"><a href=\"#-1624-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%9D%98-%EB%82%B4%EB%B6%80-%EA%B5%AC%ED%98%84\" aria-label=\" 1624 콜드 플로우의 내부 구현 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.2.4 콜드 플로우의 내부 구현</h3>\n<ul>\n<li>코틀린의 콜드 플로우는 일시 중단 함수와 수신 객체 지정 람다를 결합한 똑똑한 조합이다.</li>\n<li>콜드 플로우의 정의는 매우 간단하다.\n<ul>\n<li><code>Flow</code>와 <code>FlowCollector</code>라는 2가지 인터페이스만 필요</li>\n</ul>\n</li>\n<li><code>collect</code>를 호출하면 플로우 빌더 함수의 본문이 실행</li>\n<li>이 코드가 <code>emit</code>을 호출하면 <code>emit</code>에 전달된 파라미터로 <code>collect</code>에 전달된 람다가 호출</li>\n<li>람다 표현식이 실행을 완료하면 함수는 빌더 함수의 본문으로 돌아가 계속 실행</li>\n<li>콜드 플로우는 값 스트림을 처리하는 가벼우면서도 아주 쓸모 있고 확장성 있는 추상화를 제공</li>\n</ul>\n<h3 id=\"-1625-채널-플로우를-사용한-동시성-플로우\" style=\"position:relative;\"><a href=\"#-1625-%EC%B1%84%EB%84%90-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%8C%EB%A1%9C%EC%9A%B0\" aria-label=\" 1625 채널 플로우를 사용한 동시성 플로우 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.2.5 채널 플로우를 사용한 동시성 플로우</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">suspend fun getRandomNumber(): Int {\n    delay(500.milliseconds)\n    return Random.nextInt()\n}\n\nval randomNumbers = flow {\n    repeat(10) {\n        emit(getRandomNumber())\n    }\n}\n\nfun main() = runBlocking {\n    randomNumbers.collect {\n        log(it)\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] -1441864547\n513 [main @coroutine#1] 1101626017\n1020 [main @coroutine#1] 313359960\n1527 [main @coroutine#1] -2002910241\n2033 [main @coroutine#1] 890150628\n2538 [main @coroutine#1] -1433970862\n3045 [main @coroutine#1] 1503636281\n3548 [main @coroutine#1] -1528949568\n4055 [main @coroutine#1] -1562012272\n4561 [main @coroutine#1] 2076525050</code>\n        </deckgo-highlight-code>\n<ul>\n<li>플로우는 순차적으로 실행되며, 모든 계산은 동일 코루틴에서 실행</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">val randomNumbers = channelFlow {\n    repeat(10) {\n        launch {\n            send(getRandomNumber())\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>동시성으로 호출할 수 있는 플로우 빌더: <code>channelFlow</code></li>\n<li>채널 플로우는 내부적으로 또 다른 동시성 기본 요소인 채널을 관리해야 하기 때문에 생성하는 데 약간 비용이 든다.\n<ul>\n<li>채널은 코루틴 간 통신을 위한 비교적 저수준의 추상화</li>\n</ul>\n</li>\n<li>플로우 안에서 새로운 코루틴을 시작해야 하는 경우에만 채널 플로우 선택</li>\n</ul>\n<h2 id=\"-163-핫-플로우\" style=\"position:relative;\"><a href=\"#-163-%ED%95%AB-%ED%94%8C%EB%A1%9C%EC%9A%B0\" aria-label=\" 163 핫 플로우 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 16.3 핫 플로우</h2>\n<ul>\n<li>핫 플로우에서는 각 수집자가 플로우 로직 실행을 독립적으로 촉발하는 대신, 여러 구독자라고 불리는 수집자들이 배출된 항목을 공유한다.\n<ul>\n<li>공유 플로우: 값을 브로드캐스트하기 위해 사용</li>\n<li>상태 플로우: 상태를 전달하는 특별한 경우에 사용</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"-1631-공유-플로우는-값을-구독자에게-브로드캐스트한다\" style=\"position:relative;\"><a href=\"#-1631-%EA%B3%B5%EC%9C%A0-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%8A%94-%EA%B0%92%EC%9D%84-%EA%B5%AC%EB%8F%85%EC%9E%90%EC%97%90%EA%B2%8C-%EB%B8%8C%EB%A1%9C%EB%93%9C%EC%BA%90%EC%8A%A4%ED%8A%B8%ED%95%9C%EB%8B%A4\" aria-label=\" 1631 공유 플로우는 값을 구독자에게 브로드캐스트한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.3.1 공유 플로우는 값을 구독자에게 브로드캐스트한다</h3>\n<ul>\n<li>공유 플로우는 구독자가 존재하는지 여부에 상관없이 배출이 발생하는 브로드캐스트 방식</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class RadioStation {\n    private val _messageFlow = MutableSharedFlow&lt;Int&gt;()\n    val messageFlow = _messageFlow.asSharedFlow()\n\n    fun beginBroadcasting(scope: CoroutineScope) {\n        scope.launch {\n            while (true) {\n                delay(500.milliseconds)\n                val number = Random.nextInt(0..10)\n                log(&quot;Emitting $number!&quot;)\n                _messageFlow.emit(number)\n            }\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>플로우 빌더를 사용하는 대신 가변적인 플로우에 대한 참조를 얻는다.</li>\n<li>배출이 구독자 유무와 관계없이 발생하므로 여러분이 실제 배출을 수행하는 코루틴을 시작할 책임이 있다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    RadioStation().beginBroadcasting(this)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>구독자가 없어도 브로드캐스트가 즉시 시작</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    val radioStation = RadioStation()\n    radioStation.beginBroadcasting(this)\n    delay(600.milliseconds)\n    radioStation.messageFlow.collect {\n        log(&quot;A collecting: $it!&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>약간의 지연 후에 공유 플로우 구독</li>\n</ul>\n<h4 id=\"️-구독자를-위한-값-재생\" style=\"position:relative;\"><a href=\"#%EF%B8%8F-%EA%B5%AC%EB%8F%85%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%92-%EC%9E%AC%EC%83%9D\" aria-label=\"️ 구독자를 위한 값 재생 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🛠️ 구독자를 위한 값 재생</h4>\n<ul>\n<li>공유 플로우 구독자는 구독을 시작한 이후에 배출된 값만 수신</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">private val _messageFlow = MutableSharedFlow&lt;Int&gt;(replay = 5)</code>\n        </deckgo-highlight-code>\n<ul>\n<li>이전에 배출된 원소도 수신하기를 원한다면 <code>replay</code> 파라미터 설정</li>\n</ul>\n<h4 id=\"️-sharein으로-콜드-플로우를-공유-플로우로-전환\" style=\"position:relative;\"><a href=\"#%EF%B8%8F-sharein%EC%9C%BC%EB%A1%9C-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EA%B3%B5%EC%9C%A0-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A1%9C-%EC%A0%84%ED%99%98\" aria-label=\"️ sharein으로 콜드 플로우를 공유 플로우로 전환 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🛠️ shareIn으로 콜드 플로우를 공유 플로우로 전환</h4>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun querySensor(): Int = Random.nextInt(-10..30)\n\nfun getTemperatures(): Flow&lt;Int&gt; {\n    return flow {\n        while (true) {\n            emit(querySensor())\n            delay(500.milliseconds)\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>일정한 간격으로 값을 반환</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun celsiusToFahrenheit(celsius: Int) = celsius * 9.0 / 5.0 + 32.0\n\nfun main() {\n    val temps = getTemperatures()\n    runBlocking {\n        launch {\n            temps.collect {\n                log(&quot;$it Celsius&quot;)\n            }\n        }\n        launch {\n            temps.collect {\n                log(&quot;${celsiusToFahrenheit(it)} Fahrenheit&quot;)\n            }\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>여러번 호출하려면 각 수집자가 센서에 독립적으로 질의</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val temps = getTemperatures()\n    runBlocking {\n        val sharedTemps = temps.shareIn(this, SharingStarted.Lazily)\n        launch {\n            sharedTemps.collect {\n                log(&quot;$it Celsius&quot;)\n            }\n        }\n        launch {\n            sharedTemps.collect {\n                log(&quot;${celsiusToFahrenheit(it)} Fahrenheit&quot;)\n            }\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>shareIn</code> 함수를 사용하면 주어진 콜드 플로우를 한 플로우인 공유 플로우로 변환할 수 있다.</li>\n<li>두번째 파라미터 <code>started</code>는 플로우가 실제로 언제 시작돼야 하는지를 정의\n<ul>\n<li><code>Eagerly</code>는 플로우 수집을 즉시 시작</li>\n<li><code>Lazily</code>는 첫 번째 구독자가 나타나야만 수집 시작</li>\n<li><code>WhileSubscribed</code>는 첫 번째 구독자가 나타나야 수집을 시작하고, 마지막 구독자가 사라지면 플로우 수집을 취소</li>\n</ul>\n</li>\n<li>코틀린에서는 시간이 지남에 따라 여러 값을 계산하는 작업을 단순한 콜드 플로우로 노출하고, 필요할 때 콜드 플로우를 핫 플로우로 변환하는 패턴이 자주 사용된다.</li>\n</ul>\n<h3 id=\"-1632-시스템-상태-추적-상태-플로우\" style=\"position:relative;\"><a href=\"#-1632-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%83%81%ED%83%9C-%EC%B6%94%EC%A0%81-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0\" aria-label=\" 1632 시스템 상태 추적 상태 플로우 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.3.2 시스템 상태 추적: 상태 플로우</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class ViewCounter {\n    private val _counter = MutableStateFlow(0)\n    val counter = _counter.asStateFlow()\n    fun increment() {\n        _counter.update { it + 1 }\n    }\n}\n\nfun main() {\n    val vc = ViewCounter()\n    vc.increment()\n    println(vc.counter.value)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>값을 배출하는 <code>emit</code>을 사용하는 대신, 값을 갱신하는 <code>update</code> 함수 사용</li>\n</ul>\n<h4 id=\"️-update-함수로-안전하게-상태-플로우에-쓰기\" style=\"position:relative;\"><a href=\"#%EF%B8%8F-update-%ED%95%A8%EC%88%98%EB%A1%9C-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%97%90-%EC%93%B0%EA%B8%B0\" aria-label=\"️ update 함수로 안전하게 상태 플로우에 쓰기 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🛠️ UPDATE 함수로 안전하게 상태 플로우에 쓰기</h4>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">    fun increment() {\n    _counter.value++\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>value</code> 는 가변 속성이니까 위와 같이 구현해도 될까?</li>\n<li>위 연산은 원자적이지 않다.</li>\n<li>즉, 코루틴들이 여러 스레드에서 실행되기 때문에 문제가 있다.\n<ul>\n<li>어느 한쪽의 증가 연산이 무효화</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"️-상태-플로우는-값이-실제로-달라졌을-때만-값을-배출한다-동등성-기반-통합\" style=\"position:relative;\"><a href=\"#%EF%B8%8F-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%8A%94-%EA%B0%92%EC%9D%B4-%EC%8B%A4%EC%A0%9C%EB%A1%9C-%EB%8B%AC%EB%9D%BC%EC%A1%8C%EC%9D%84-%EB%95%8C%EB%A7%8C-%EA%B0%92%EC%9D%84-%EB%B0%B0%EC%B6%9C%ED%95%9C%EB%8B%A4-%EB%8F%99%EB%93%B1%EC%84%B1-%EA%B8%B0%EB%B0%98-%ED%86%B5%ED%95%A9\" aria-label=\"️ 상태 플로우는 값이 실제로 달라졌을 때만 값을 배출한다 동등성 기반 통합 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🛠️ 상태 플로우는 값이 실제로 달라졌을 때만 값을 배출한다: 동등성 기반 통합</h4>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">enum class Direction { LEFT, RIGHT }\n\nclass DirectionSelector {\n    private val _direction = MutableStateFlow(Direction.LEFT)\n    val direction = _direction.asStateFlow()\n\n    fun turn(d: Direction) {\n        _direction.update { d }\n    }\n}\n\nfun main() = runBlocking {\n    val switch = DirectionSelector()\n    launch {\n        switch.direction.collect {\n            log(&quot;Direction now $it&quot;)\n        }\n    }\n    delay(200.milliseconds)\n    switch.turn(Direction.RIGHT)\n    delay(200.milliseconds)\n    switch.turn(Direction.LEFT)\n    delay(200.milliseconds)\n    switch.turn(Direction.LEFT)\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#2] Direction now LEFT\n210 [main @coroutine#2] Direction now RIGHT\n405 [main @coroutine#2] Direction now LEFT</code>\n        </deckgo-highlight-code>\n<ul>\n<li>LEFT 인자가 한번만 호출이 된다.</li>\n<li>상태 플로우가 동등성 기반 통합을 수행하기 때문\n<ul>\n<li>값이 실제로 달라졌을 때만 구독자에게 값을 배출</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"️-statein-으로-콜드-플로우를-상태-플로우로-변환하기\" style=\"position:relative;\"><a href=\"#%EF%B8%8F-statein-%EC%9C%BC%EB%A1%9C-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%98%EA%B8%B0\" aria-label=\"️ statein 으로 콜드 플로우를 상태 플로우로 변환하기 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🛠️ stateIn 으로 콜드 플로우를 상태 플로우로 변환하기</h4>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    val temps = getTemperatures()\n\n    runBlocking {\n        val tempState = temps.stateIn(this)\n        println(tempState.value)\n        delay(800.milliseconds)\n        println(tempState.value)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>stateIn</code>으로 콜드 플로우를 상태 플로우로 변환할 수 있다.</li>\n<li>원래 플로우에서 배출된 최신 값을 항상 읽을 수 있다.</li>\n</ul>\n<h3 id=\"-1633-상태-플로우와-공유-플로우의-비교\" style=\"position:relative;\"><a href=\"#-1633-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%99%80-%EA%B3%B5%EC%9C%A0-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%9D%98-%EB%B9%84%EA%B5%90\" aria-label=\" 1633 상태 플로우와 공유 플로우의 비교 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.3.3 상태 플로우와 공유 플로우의 비교</h3>\n<ul>\n<li>일반적으로 상태 플로우는 공유 플로우보다 더 간단한 API를 제공</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class Broadcaster {\n    private val _messages = MutableSharedFlow&lt;String&gt;()\n    val messages = _messages.asSharedFlow()\n    fun beginBroadcasting(scope: CoroutineScope) {\n        scope.launch {\n            _messages.emit(&quot;Hello!&quot;)\n            _messages.emit(&quot;Hi!&quot;)\n            _messages.emit(&quot;Hola!&quot;)\n        }\n    }\n}\n\nfun main(): Unit = runBlocking {\n    val broadcaster = Broadcaster()\n    broadcaster.beginBroadcasting(this)\n    delay(200.milliseconds)\n    broadcaster.messages.collect {\n        println(&quot;Message: $it&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>첫 번째 구독자가 나타나기 전에 모든 메시지를 배출하는 브로드캐스트</li>\n<li>메시지를 출력하지 않음.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class Broadcaster {\n    private val _messages = MutableStateFlow&lt;List&lt;String&gt;&gt;(emptyList())\n    val messages = _messages.asStateFlow()\n    fun beginBroadcasting(scope: CoroutineScope) {\n        scope.launch {\n            _messages.update { it + &quot;Hello!&quot; }\n            _messages.update { it + &quot;Hi!&quot; }\n            _messages.update { it + &quot;Hola!&quot; }\n        }\n    }\n}\n\nfun main() = runBlocking {\n    val broadcaster = Broadcaster()\n    broadcaster.beginBroadcasting(this)\n    delay(200.milliseconds)\n    println(broadcaster.messages.value)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>상태 플로우는 전체 메시지 기록을 리스트로 저장하면서 구독자가 모든 이전 메시지에 쉽게 접근할 수 있게 할 수 있다.</li>\n</ul>\n<h3 id=\"-1634-핫-플로우-콜드-플로우-공유-플로우-상태-플로우-언제-어떤-플로우를-사용할까\" style=\"position:relative;\"><a href=\"#-1634-%ED%95%AB-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EA%B3%B5%EC%9C%A0-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%96%B8%EC%A0%9C-%EC%96%B4%EB%96%A4-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C\" aria-label=\" 1634 핫 플로우 콜드 플로우 공유 플로우 상태 플로우 언제 어떤 플로우를 사용할까 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.3.4 핫 플로우, 콜드 플로우, 공유 플로우, 상태 플로우: 언제 어떤 플로우를 사용할까?</h3>\n<h3 id=\"표-161-콜드-플로우와-핫-플로우-비교\" style=\"position:relative;\"><a href=\"#%ED%91%9C-161-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%99%80-%ED%95%AB-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EB%B9%84%EA%B5%90\" aria-label=\"표 161 콜드 플로우와 핫 플로우 비교 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>표 16.1 콜드 플로우와 핫 플로우 비교</h3>\n<table>\n<thead>\n<tr>\n<th>콜드 플로우</th>\n<th>핫 플로우</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>기본적으로 비활성(수집자에 의해 활성화됨)</td>\n<td>기본적으로 활성화됨</td>\n</tr>\n<tr>\n<td>수집자가 하나 있음</td>\n<td>여러 구독자가 있음</td>\n</tr>\n<tr>\n<td>수집자는 모든 배출을 받음</td>\n<td>구독자는 구독 시작 시점부터 배출을 받음</td>\n</tr>\n<tr>\n<td>보통은 완료됨</td>\n<td>완료되지 않음</td>\n</tr>\n<tr>\n<td>하나의 코루틴에서 배출 발생(channelFlow 사용 시 예외)</td>\n<td>여러 코루틴에서 배출할 수 있음</td>\n</tr>\n</tbody>\n</table>","excerpt":"📖 16.1 플로우는 연속적인 값의 스트림을 모델링한다 모든 값이 계산 된 후 함수가 값을 반환함. 함수가 실행을 마칠 때까지 기다리지 않고 값을 사용할 수 있도록 비동기적으로 반환하고 싶을 때 플롱우가 유용하다. 플로우는 시간이 지남에 따라 나타나는 값과 작업할 수 있게 해주는 코루틴 기반의 추상화다 🔖 16.1.1 플로우를 사용하면 배출되자마자 원소를 처리할 수 있다 원소가 배출되는 즉시 표시된다. 🔖 16.1.…","fields":{"slug":"/backend/kotlin-in-action/16장-플로우/"},"frontmatter":{"title":"Kotlin in Action - 16장 플로우","thumbnail":{"childImageSharp":{"fluid":{"src":"/static/f7f7ddfa31d1614405dc5af1487b9ec4/9c94d/kotlin-in-action.png"}}},"draft":false,"category":"Back-End","tags":["Kotlin"],"date":"June 22, 2025"}}}},"staticQueryHashes":["2374173507","2996537568","3691437124"]}