- Kotlin Coroutine은 일시중단을 구현하기 위해 ContinuosPassing style을 적용하였음
CPS스타일로 변환된 Suspend 함수
1
2
3
4
5
6
7
8
| suspend fun printUser(token: String) {
println("Before")
val userId = getUserId(token) // suspending
println("Got userId: $userId")
val userName = getUserName(userId, token) // suspending
println(User(userId, userName))
println("After")
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| fun printUser(
token: String,
continuation: Continuation<*>
): Any {
val continuation = continuation as? PrintUserContinuation
?: PrintUserContinuation(
continuation as Continuation<Unit>,
token
)
var 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, 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)
}
}
|
- 함수의 오퍼레이션이 변경됨
- 마지막 인자로 continuation이 생김
- continuation은 현재 코루틴의 상태를 가지고 있는 상태머신임
- 항상 function의 마지막 인자로 추가됨
- return 타입이 Any로 변경됨
- Any?로 바뀌는 이유는 실제 리턴타입 뿐만아니라, suspend된다면 COROUTINE_SUSPENDED을 반환해야하기 때문
추후 kotlin에 유니온 타입이 추가된다면 User?|COROUTINE_SUSPENDED가 될 수 있음
5번 라인, Continuation이 해당 함수의 Continuation인지 확인하고, 아니라면 생성
- resume될떄는 해당 함수의 Continuation이므로, 처음 실행될때만 생성함
11,12,13번 라인, 지역변수들을 선언하고, 값을 대입
- 11번 result변수는 직전에 호출한 suspend 함수의 결과 가짐
- 12번 userId는 여러 단계(1,2)에 걸쳐서 필요하므로 Continuation에 저장됨
- 13번 userName은 한번의 단계에서만 사용하므로 result로 가져올수 있어 따로 저장되지 않음
Continuation은 label을 가짐
- label로 현재 어디까지 코드가 진행되었는지 파악하고, 다음 실행때 어디부터 시작할지 결정함
suspend된다면, COROUTINE_SUSPENDED을 리턴 후 중단이 끝난 후 다시시작함
- io작업이 발생한다면 작업을 끝내지 못하므로 우선 COROUTINE_SUSPENDED을 리턴함
suspend이후 resume된다면, PrintUserContinuation의 resumeWith가 호출됨
- 22번 라인과 56번라인이 동일 기능을 함
- 앞서 말한다로 직전에 호출한 suspend 함수의 결과를 result변수에 넣음
콜스택 마지막에 있는 함수의 continuation이 supend후 resume되고, 작업을 다 끝마치면, 바로 상위 함수의 continuation의 resume을 호출함
https://kt.academy/article/cc-under-the-hood