안드로이드 공부를 할때마다 계속 코루틴 코루틴이라는 단어가 나온다. 모르고 걍 넘어갔다가 코드가 꼬여 애먹은 적이 여러번이다. 이젠 제대로 정리해보려고 한다.
물론 절대적인 정의는 없다. 다만 이해를 위해 정리해본다.
루틴: 특정 작업을 수행하고 재사용될 수 있는 코드
서브루틴: 주 루틴보다 작은 루틴. 주 루틴의 일부이며, 다른 서브루틴과는 독립적이다. 반복적으로 호출될 수 있다.
요컨데 여러 루틴들이 서로 협력적으로 동작하는 제어 구조~패턴이다.
그러면 이게 코틀린에서는 어떻게 적용되는지 알아보자.
코틀린은 async나 await가 아닌 중단 함수suspend function 개념을 사용해 비동기 작업을 실행한다.
코틀린 공식 문서는 코루틴을 중단 가능한 계산의 인스턴스라고 설명한다. 나머지 코드와 동시에 실행된다는 점에서 스레드와 유사하지만, 엄밀히 말하면 다르다. 스레드는 특정 스레드에 종속되지 않는다. 코루틴은 한 스레드에서 실행을 중단하고 다른 스레드에서 시작할 수 있다.
아래는 문서의 공식 코드다.
fun main() = runBlocking { //<- CoroutineScope
launch { // 새로 코루틴을 시작
delay(1000L) // 1초동안 차단x 지연
println("World!") // 지연 후 출력
}
println("Hello") // 이전 코루틴이 지연되는 동안 주요 코루틴은 실행된다
}
결과는
Hello
World!
launch: 코루틴 빌더. 다른 코드와 함께 동시적으로 새 코루틴을 실행. 그 때문에 Hello가 먼저 출력delay: 중단 함수. 일정 시간동안 코루틴을 중단코루틴 중단은 관련된 스레드를 차단하지 않는다. 하지만 관련 스레드에 있는 다른 코루틴이 실행하는 것을 허락한다.
runBlocking: 코루틴이 아닌 main()을 연결하는 코루틴 빌더. 해당 빌더는 실행하는 스레드가 runBlocking{ ... } 내의 모든 코루틴이 실행을 완료할 때까지 스레드를 차단 (물론 스레드 차단은 비용이 크다. 추천x)위의 코드에서, println("World!")를 밖으로 내보내고 싶은 경우에는 다음과 같이 작성한다.
fun main() = runBlocking { // <- CoroutineScope
launch { doWorld() }
println("Hello")
}
suspend fun doWorld() {
delay(1000L)
println("World!")
}
suspend중단: 코루틴 안에서 일반 함수처럼 사용할 수 있고, 다른 중단 함수를 사용해 코루틴 실행을 중단할 수도 있음자신만의 코루틴스코프를 생성할 수도 있다.
fun main() = runBlocking {
doWorld()
}
suspend fun doWorld() = coroutineScope { // CoroutineScope
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
...전에 코루틴스코프가 무엇이냐??
코루틴스코프CoroutineScope는 코루틴컨텍스트CoroutineContext 하나만 멤버 속성의 정의하고 있는 인퍼테이스다.
코루틴컨텍스트는 코루틴을 어떻게 처리 할지에 대한 정보를 가지고 있는 객체다.
위에서는 coroutineScope를 사용해 자신만의 범위scope를 생성했다. { ... } 안의 모든 함수가 실행되기 전까지 종료되지 않는다.
출력은 맨 위의 예시 코드와 같다.
// 순차적으로 코드를 실행한다
fun main() = runBlocking {
doWorld()
println("Done")
}
// 동시에 두 부분을 실행한다
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
launch {
delay(2000L)
println("World 2")
}
launch {
delay(1000L)
println("World 1")
}
println("Hello")
}
실행하는 코루틴 빌더는 코루틴 핸들인 Job객체를 반환한다. 이를 이용해 실행된 코루틴의 완료를 명시적으로 기다릴 수 있다.
val job = launch { // 새 코루틴을 실행하고 Job에 대한 참조를 유지
delay(1000L)
println("World!")
}
println("Hello")
job.join() // 코루틴이 완료될 때까지 기다린다
println("Done")
출력은 다음과 같다.
Hello
World 1
World 2
Done
이제 아주 기본적인 것들은 이렇다... 이제 다음으로 넘어가자니 문서가 자 여기부터는 물이 정말 깊어를 시전했다. 우선 몇번 읽어보고 정리하려고 한다....
안드로이드에서도 종종 쓰이는 관련 함수들을 정리했다.
fun <T> Flow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted,
initialValue: T
): StateFlow<T>
콜드 플로우를 핫 스테이트 플ㄹ우로 변환하는 함수다. 주어진 CoroutineScope를 기준으로 시작해, 가장 최근 방출된emitted 값을 공유한다.
콜드 플로우: 내놓으라고 말해야 내 입에 집어넣어줌
핫 플로우: 내놓으라고 말하기 전에 이거 새거라며 내 입에 집어넣음
일부 상태 값에 대한 업데이트를 제공하는 콜드 플로우가 있고, 생성 및 유지 관리 비용이 많이 들지만 가장 최근 상태 값을 수집해야 하는 구독자가 여러 명 있는 경우에 유용
Eagerly: 첫번째 구독자가 나타나기 전에도 시작. 이 경우 replay 파라미터에서 지정한 개수 외의 값은 즉시 삭제Lazily: 첫번쩨 구독자가 나타난 후에 시작. 첫번째 구독자는 모든 방출된 값을 가질 수 있으나, 그 다음 구독자는 최근 값만 가질 수 있음WhileSubscribed(): 첫번째 구독자가 나타난 후에 시작, 마지막 구독자가 사라지면 즉시 멈춤. stopTimeoutMillis를 통해 마지막 구독자가 사라지고 공유 코루틴이 중지될 때까지의 지연 시간을 설정할 수 있음suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T
디스패처Dispatcher: 코루틴이 실행하는데 사용할 스레드를 결정하는 매커니즘
val result = someComputation()
withContext(Dispatchers.Main) { updateUI(result) }scope.launch(Dispatchers.Default) {
val result = someComputation()
withContext(Dispatchers.Main) {
updateUI(result)
}
}
출처
- https://developer.android.com/kotlin/coroutines?hl=ko
- https://ko.wikipedia.org/wiki/%EC%BD%94%EB%A3%A8%ED%8B%B4
- https://stackoverflow.com/questions/553704/what-is-a-coroutine
- https://kotlinlang.org/docs/coroutines-basics.html
- https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/state-in.html
- https://stackoverflow.com/questions/67665004/why-use-withcontext-vs-async-await-isnt-the-goal-of-coroutines-to-achieve-co