시배's Android
Kotlin Coroutines Deep Dive | 4장. 코루틴의 실제 구현 본문
Book/Kotlin Coroutines Deep Dive
Kotlin Coroutines Deep Dive | 4장. 코루틴의 실제 구현
si8ae 2024. 1. 18. 22:15- 중단 함수는 함수가 시작할 때와 중단 함수가 호출되었을 때 상태를 가진다는 점에서 상태 머신과 비슷합니다.
- 컨티뉴에이션 객체는 상태를 나타내는 숫자와 로컬 데이터를 가지고 있습니다.
- 함수의 컨티뉴에이션 객체가 이 함수를 부르는 다른 함수의 컨티뉴에이션 객체를 장삭합니다. 그 결과, 모든 컨티뉴에이션 객체는 실행을 재개하거나 재개된 함수를 완료할 때 사용되는 콜 스택으로 사용됩니다.
Continuation Passing Style
suspend fun getUser() : User?
suspend fun setUser(user : User)
fun getUser(continuation : Continuation<*>) : Any?
fun setUser(user : User, continuation : Continuation<*>) : Any
- 중단 함수 내부를 들여다 보면 원래 선언했던 형태와 반환 타입이 다라졌습니다.
- 이는 중단 함수를 실행하는 도중에 중단되면 선언된 타입의 값을 반환하지 않을 수 있기 때문입니다.
- User? 또는 COROUTINE_SUSPENDED를 반환할 수 있기 때문에 결과 타입이 User?와 Any의 가장 가까운 슈퍼타입인 Any?로 지정되었습니다.
- 상태를 저장하기 위해 자신만의 컨티뉴에이션 객체가 필요합니다.
val continutation =
if (continuation is MyFunctionContinuation) continuation
else MyFunctionContinuation(continuation)
fun myFunction(continuation : Continuation<Unit>) : Any {
val continuation = continuation as? MyFunctionContinuation
?: MyFunctionContinuation(continuation)
if(continuation.label == 0){
println("Before")
continuation.label = 1
if(delay(1000, continuation) == COROUTINE_SUSPENDED){
return COROUTINE_SUSPENDED
}
}
if(continuation.label == 1){
println("After")
return Unit
}
error("Impossible")
}
class MyFunctionContinuation(
val completion : Continuation<Unit>
) : Continuation<Unit> {
override val context : CoroutineContext
get() = completion.context
var label = 0
var result: Result<Any>? = null
var counter = 0
override fun resumeWith(result : Result<Unit>) {
this.result = result
val res = try {
val r = myFunction(this)
if(r == COROUTINE_SUSPENDED) return
Result.Success(r as Unit)
} catch (e : Throwable) {
Result.failure(e)
}
completion.resumeWith(res)
}
}
- delay에 의해 중단된 경우 COROUTINE_SUSPENED가 반환되며, myFunction은 COROUTINE_SUSPENED를 반환 합니다.
값을 받아 재개되는 함수
suspend fun printUser(token : String) {
println("Before")
val userId = getUserId(token)
println("Got userId : $userId)
val userName = getUserName(userId, token)
println(User(userId, userName))
println("After")
}
- 함수가 값으로 재개되었다면 결과는 Result.Success(value)가 되며, 이 값을 얻어 사용할 수 있습니다.
fun printUser(
token : String,
continuation : Continuation<*>
) : Any {
val continuation = continuation as? PrintUserContinuation
?: PrintUserContinuation(
continuation as Continuation<Unit>,
token
)
val result : Result<Any>? = continuation.result
var userId : String? = continuation.userId
val userName : String
if(continuation.label == 0) {
println("Before")
continuation.label = 1
val res = getUserId(token, continuation)
if(res == COROUTINE_SUSPENDED) {
return COROUTINE_SUSPENDED
}
result = Result.success(res)
}
if(continuation.label == 1) {
userId = result!!.getOrThrow() as String
println("Got userId : $userId")
continuation.label = 2
continuation.userId = userId
val res = getUserName(userId, token, continuation)
if(res == COROUTINE_SUSPENDED){
return COROUTINE_SUSPENDED
}
result = Result.success(res)
}
if(continuation.label == 2) {
userName = result!!.getOrThrow() as String
println(User(userId as String, userName))
println("After")
return Unit
}
error("Impossible")
}
class PrintUserContinuation(
val completion : Continuation<Unit>,
val token : String
) : Continuation<String> {
override val context : CoroutineContext
get() = completion.context
var label = 0
var result : Result<Any>? = null
var userId : String? = null
override fun resumeWith(result : Result<String>){
this.result = result
val res = try {
val r = printUser(token , this)
if(r == COROUTINE_SUSPENDED) return
Result.success(r as Unit)
} catch(e : Throwable) {
Result.failure(e)
}
completion.resumeWith(res)
}
}
콜스택
- 함수 a가 b를 호출하면 가상 머신은 a의 상태와 b가 끝나면 실행이 될 지점을 어딘가에 저장해야 합니다. 이런 정보들은 모두 콜 스택이라는 자료 구조에 저장됩니다.
- 코루틴을 중단하면 스레드를 반환해 콜 스택에 있는 정보가 사라질 것입니다.
- 따라서 코루틴을 재개할 때 콜스택을 사용할 수 없습니다.
- 대신 컨티뉴에이션 객체가 콜 스택의 역할을 대신합니다.
- 컨티뉴에이션 객체는 중단이 되었을 때의 상태(label)와 함수의 지역 변수와 파라미터, 그리고 중단 함수를 호출한 함수가 재개될 위치 정보를 가지고 있습니다.
- 컨티뉴에이션 객체가 재개될 때 각 컨티뉴에이션 객체는 자신이 담당하는 함수를 먼저 호출합니다.
- 함수의 실행이 끝나면 자신을 호출한 함수의 컨티뉴에이션을 재개합니다.
실제 코드
- 예외가 발생했을 때 더 나은 스택 트레이스 생성
- 코루틴 중단 인터셉션
- 사용하지 않는 변수를 제거하거나 테일콜 최적화하는 등의 다양한 단계에서의 최적화
'Book > Kotlin Coroutines Deep Dive' 카테고리의 다른 글
Kotlin Coroutines Deep Dive | 6장. 코루틴 빌더 (0) | 2024.01.21 |
---|---|
Kotlin Coroutines Deep Dive | 5장. 언어차원에서의 지원 vs 라이브러리 (0) | 2024.01.19 |
Kotlin Coroutines Deep Dive | 3장. 중단은 어떻게 작동할까? (0) | 2024.01.18 |
Kotlin Coroutines Deep Dive | 2장. 시퀀스 빌더 (0) | 2024.01.15 |
Kotlin Coroutines Deep Dive | 1장. 코틀린 코루틴을 배워야 하는 이유 (0) | 2024.01.15 |