마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

suspendingCoroutine과 Continuation이 무엇인가요

0 추천
Coroutine 개념이 많이 부족한것같아서 처음부터 개념 읽어가면서 보고있는데

이 두개념은 좀 적어도 국내자료는 거의 없고 쉽게 설명된곳도 없더라구요..

이 두개는 도대체 무엇인가요..?
codeslave (3,940 포인트) 님이 2022년 2월 8일 질문

1개의 답변

0 추천

Coroutine은 내부적으로 콜백으로 동작을 합니다. Coroutine이 컴파일된 코드를 보면 결국 자바 코드가 되는데, Continuton 이 콜백에 대한 정보를 담고있는 클래스라고 보시면 됩니다. 

코루틴 개발자가 프레젠테이션을 했던 예제를 가지고 설명을 해볼게요..  글을 서버에 올리는 서비스가 있다고 하죠. 이 서비스를 이용하기 위해서는 먼저 토큰을 가져와서, 서버에 글을 올리고, 생성된 글을 가지고 추가 처리를 해야한다고 가정해 보죠.

이 경우, 콜백을 이용한 코드를 작성하면 아래처럼 될 겁니다.

fun PostItem(item: Item) {
    requestToken { token ->
        createPost(toke, item) { post ->
             processPost(post)
        }
    }
}

콜백에 콜백을 호출하는 되는 'Callack hell"이라고 불리우는 중첩 콜백을 작성하게 됩니다. 코드를 읽기도 힒들고 실수를 하기도 쉽죠. 위의 코드를 아래처럼 비동기이지만 순차적으로 코드를 작성할 수 있다면 훨씬 가독성이 뛰어나고 유지보수가 쉬운 코드를 작성할 수 있게 됩니다.

suspend fun PostItem(item: Item) {
    val token = requestToken()
    val token = createPost(toke, item)
    processPost(post)
}

Continuation은 이것을 가능하게 해주는 특별한 callback wrapper입니다. Continuation의 아랫처럼 생겼습니다.

interface Continuation<in T> {
    val context: CoroutineContext,
    fun resume(value: T)
    fun resumeWithException(exception: Throwable)
}

위의 createPost함수는 실제로는 Continuation을 사용하여 아래와 같이 변환됩니다.

suspend fun createPost(token: Token, item: Item): Post  {...}

Object createPost(Token token, Item item, Continuation<Post> continuation) { ... }

즉, 코틀린 함수가 suspend 키워드를 가지고 있으면, 자바로 변환될 때 Continuation 인자를 만들어 주게 됩니다. Continuation 인터페이스에서 보시는 것처럼, 이 인터페이스는 다음에 실행할 함수에 대한 정보를 담고 있습니다. 

postItem함수는 실제로 아래와 같은 형태로 동작한다고 보시면 됩니다.

fun postItem(item: Item, continuation: Continuation) {

   val sm = object: CoroutineImpl {.... }
   switch (sm.label) {
       case 0:
             sm.item  = item
             sm.label = 1
             requestToken(sm)
        case 1: 
            val item = sm.item
            val token = sm.result as Token
            sm.label = 2
            createPost(token, item, sm)
       case 2:
            processPost(post)
    }
}

postItem함수가 재귀적으로 호출되는데, continuation 인자에 따라 적절한 함수를 호출하고 있습니다.

suspendCoroutine은 콜백을 Coroutine 으로 변환하도록 해주는 함수입니다. 결국은 이것도 Continuation이 내부적으로 사용됩니다.

spark (223,680 포인트) 님이 2022년 2월 8일 답변
spark님이 2022년 2월 8일 수정
fun PostItem(item: Item) {
    requestToken { token ->
        createPost(toke, item) { post ->
             processPost(post)
        }
    }
}


suspendCoroutine requestToken { continuation ->
    return suspendCoroutine { continuation ->
        requestToken { token ->
            processPost {  _ ->
            }
    }
}
이런식으로 각각의 콜백에 대해 suspendCoroutine이 작성되는게 아니라 requestToken이 suspend 함수가 되고 그 다음부터가 suspendCoroutine이 되네요..? 동작을 이해하기 아직도 어렵네요..
제가 위에 suspendCoroutine을 콜백 하나에만 적용하는게 좋다라고 말씀은 드린 이유가 그거예요. 만약에 각각의 콜백에 대해 적용을 하고자 한다면 아래같은 구조가 되는게 일리가 있을 것 같아요.
suspend fun requestTokenCont(): Token  {
     return suspendCoroutine { cont ->
        requestToken { token->
             cont.resume(token)
        }
     }
}

suspend fun createPostCont(token: Token, item: Post): Post  {
     return suspendCoroutine { cont ->
             createPost(token, item) { post ->
                 cont.resume(post)
             }
        }
     }
}

suspend fun processPost Cont(item: Post)  {
     return suspendCoroutine { cont ->
         processPost(item) { cont ->
             cont.resume(Unit)
         }
     }
}

suspend fun PostItem(item: Item) {
    val token = requestToken()
    val post = createPost(toke, item)
    processPost(post)
}

님이 생각했던 것처럼 콜백 3개를 모두 하나로 묶어서 변환시키려면 다른 방법이 존재해요. (클래스이름이 길어서 정확하게는 기억이 안나네요.)
아 각각 나뉘어서 적용하면
맨처음 설명해주실때 말씀하신

suspend fun PostItem(item: Item) {
    val token = requestToken()
    val token = createPost(toke, item)
    processPost(post)
}
이러한 코드가 되는군요..

근데 이렇게 하지 않고

requestToken() 하면 위와 같은 모양은 안나오고 requestToken만 호출하는 구조로 되려나요?
지금은 그냥 suspend function을 이해하고 사용하는데만 집중하시고 suspendCoroutine 는 나중에 사용할 필요가 있을 때 공부를 하시는게 더 좋을 것 같아요. 코루틴의 내부적인 구현은 엄청 복잡해서 사용에 좀 주의가 필요한데, 안드로이드 같은 경우는 대부분의 겅우는 문제가 될 만한 경우가 없어요. 한번에 여러가지 개념을 이해하려 하지 마시고 한가지만 먼저 공부한 후에 필요가 생기면 그 때 하시는 게 더 나을 것 같아요. 저도 아직 배워야할 개념이 너무 많아서, 계속 공부를 하게 되요. 예를 들면, Flow, StateFlow, MutableStateFlow, SharedFlow, MutableSharedFlow 등등 새로운 개념들이 계속 추가되고 있는 상황이라 손대자면 끝이 없어요.
아아 감사합니다ㅠ 다름이 아니라 지난번에 작성해주신 코드에 suspendCoroutine이 있어서 그거 이해하려고 공부를 시작했더니 여기까지 왓네요 ㅠㅠ 감사합니다.. 아직도 근데 이해가 덜된게 함정이긴한데.. 아무튼 감사드립니다
...