이번시간에는 코루틴이 내부적으로 어떻게 동작하는지 확인해보겠습니다
[Continuation Passing Style]
CPS == Callbacks
//Kotlin
suspend fun createPost(token : Token, item : Item) : Post { _ }
// Java/JVM
Object createPost(Token token, Item item, Continuation<Post> cont) { _ }
작성한 코루틴이 컴파일 될때 내부적으로 JVM에서 Byte 코드로 변환되면서 Continuation이 하나가 더 생기게 됩니다. (CPS)
[Labels]
//Kotlin
suspend fun postItem(item : Item) {
// LABLE 0
val token = requestToken()
// LABEL 1
val post = createPost(token, item)
// LABEL 2
processPost(post)
}
// Java/JVM
suspend fun postItem(item : Item) {
switch(label){
case 0:
val token = requestToken()
case 1:
val post = createPost(token, item)
case 2:
processPost(post)
}
}
suspend 함수가 붙게되면 컴파일될때 LABEL이 찍힙니다
중단지점과 재개되는 지점이 필요하기 때문에 LABEL 작업을 코틀린 컴파일러가 내부적으로 하게 됩니다
내부적으로는 switch/case 문으로 바뀌어 순차적으로 실행하게 됩니다
LABEL이 완성되고 나면 CPS 로 변하게 됩니다
fun postItem(item : Item, cont : Continuation){
val sm = object : CoroutineImpl { .. }
switch(sm.label){
case 0:
requestToken(sm) //state machine == continuation
case 1:
createPost(token, item, sm)
case 2:
processPost(post)
}
}
suspend function을 호출할 때마다 Continuation을 넘겨줍니다
Continuation은 callback 인터페이스 같은것이라고 합니다
Continuation은 어떤 정보값을 가진 형태로 계속 패싱이 되면서 코루틴이 내부적으로 동작하게 됩니다
fun postItem(item : Item, cont : Continuation){
val sm = cont as? ThisSM ?: object : ThisSM {
fun resume(_) {
postItem(null, this)
}
}
switch(sm.label){
case 0:
sm.item = item
sm.label = 1
requestToken(sm)
case 1:
createPost(token, item, sm)
...
}
}
각 case 마다 연산이 끝났을때 continuation에 resume을 호출하게 됩니다
resume은 자기 자신을 다시 불러줍니다
그래서 label을 하나씩 늘려 다음 case를 실행시켜줍니다
[CPS simulation]
GlovalScope.launch {
val userData = fetchUserData()
val userCache = cacheUserData(userData)
updateTextView(userCache)
}
밑의 코드는 위의 코루틴을 작성했을때 내부적으로 어떻게 동작되는지 알아볼수 있는 코드입니다
fun main() {
println("[in] main")
myCoroutine(MyContinuation()) //메인을 실행하면 한번 불림 // 내부적으로 CPS
println("\n[out] main")
}
fun myCoroutine(cont: MyContinuation) {
when(cont.label) { //LABEL 처리된 suspending point
0 -> {
println("\nmyCoroutine(), label: ${cont.label}")
cont.label = 1
fetchUserData(cont)
}
1 -> {
println("\nmyCoroutine(), label: ${cont.label}")
val userData = cont.result
cont.label = 2
cacheUserData(userData, cont)
}
2 -> {
println("\nmyCoroutine(), label: ${cont.label}")
val userCache = cont.result
updateTextView(userCache)
}
}
}
fun fetchUserData(cont: MyContinuation) {
println("fetchUserData(), called")
val result = "[서버에서 받은 사용자 정보]"
println("fetchUserData(), 작업완료: $result")
cont.resumeWith(Result.success(result)) //myCoroutine를 다시 부르면서 다음 LABEL로 넘어감
}
fun cacheUserData(user: String, cont: MyContinuation) {
println("cacheUserData(), called")
val result = "[캐쉬함 $user]"
println("cacheUserData(), 작업완료: $result")
cont.resumeWith(Result.success(result))
}
fun updateTextView(user: String) {
println("updateTextView(), called")
println("updateTextView(), 작업완료: [텍스트 뷰에 출력 $user]")
}
class MyContinuation(override val context: CoroutineContext = EmptyCoroutineContext)
: Continuation<String> {
var label = 0
var result = ""
override fun resumeWith(result: Result<String>) {
this.result = result.getOrThrow()
println("Continuation.resumeWith()")
myCoroutine(this)
}
}
references :
kotlinlang.org/docs/reference/coroutines/coroutines-guide.html
www.youtube.com/channel/UCJeARDV434voq3IxRTBfzLw
'개발 > ANDROID' 카테고리의 다른 글
[Android] Retrofit2 URL Encoding 방지하기 (0) | 2021.06.15 |
---|---|
[Android] 코루틴 Coroutine Context and Dispatchers (0) | 2020.10.16 |
[Android] 코루틴 Composing Suspending Functions (0) | 2020.10.14 |
[Android] 코루틴 Cancellation and Timeouts (0) | 2020.10.09 |
[Android] 코루틴 기초 (0) | 2020.10.07 |
댓글