본문 바로가기

카테고리 없음

Kotlin Coroutine Continuation

👩‍💻 오늘의 할 일

오늘은 코루틴을 사용할 때 빠질수 없는 suspend 키워드가 조금은 낯선 Continuation에 대해 알아보겠습니다. 본 글은 [DroidKnights 2023] 김준비 - Coroutine Deep Dive - Android 실전편과 코틀린 코루틴의 정석 13장 코루틴 심화를 참고하여 작성하였습니다.

1. Suspend 함수는 어떻게 동작할까 ?

 코루틴이 suspend 함수를 통해 suspended 상태가 되면 Continuation 이란 것을 사용해 정지된 코루틴을 재개할 수 있습니다. 스레드는 Blocking 상태가 되면 사용중이던 자원을 점유하는데 반해 코루틴은 suspended 상태가 되면 어떤 자원도 사용하지 않으며 다른 코루틴이 자원을 사용할 수 있도록 오픈하기 때문에 스레드에 비해 훨씬 효율적입니다. suspend 함수에 대해 중요한 두 가지 개념이 있습니다.

👆 suspend 함수는 일시 중단 함수로 코루틴 내부에서 코루틴을 일시 정지 시킬 수 있는 함수일 뿐 코루틴이 아닙니다.
✌️ suspend 함수로 선언했다고 해서 모두가 중단 가능한 함수는 아닙니다.

2. Continuation 이란?

 저를 포함 많은 분들이 코루틴을 사용하면서 Continuation을 직접 사용해본적은 아마 없을것이라고 생각합니다. 저도 실제로 직접 사용해본 적은 한번도 없습니다. 그 이유는 우리가 지금까지 다룬 코루틴의 API는 모두 고수준 API 이기 때문입니다.

📌 고수준 API 사용하기 쉽게 설계된 인터페이스로 내부의 복잡성을 아래로 끌어내려 사용자에게 직관적이고 간단한 방법으로 기능을 제공하는 것을 의미합니다.

 

 고수준 API를 좀더 쉽게 이해해보자면 인터페이스, 저수준 API는 인터페이스를 구현하는 구현체라고 생각할 수 있을 것 같습니다. 예를 들어 코루틴에서 자주 사용하는 빌더 함수들(launch, async, withContext) 등등 모두 이 함수들이 내부적으로 어떻게 실행되고, 스케줄링 하며, 컨텍스트는 어떻게 전환되는지 몰라도 쉽게 사용할 수 있습니다.

 

코루틴 라이브러리에서 제공하는 고수준 API는 Continuation 객체를 캡슐화해 사용자에게 노출하지 않지만 내부적으로는 코루틴이 일시 중단과 재개가 Continuation  객체를 통해 이루어집니다.

Continuation 또한 내부적으로 interface로 선언되어 다음의 역할을 수행합니다.

  • CoroutineContext : CoroutineDispatcher, CoroutineName, CoroutineExceptionHandler, Job 총 네 가지의 구성요소로 이루어져 코루틴의 실행 환경을 구성하는 코루틴 구성요소
  • resumeWith(result: Result<T>): 중단된 코루틴을 다시 재개 하기 위해 호출하는 메서드입니다. 성공(Result.success) 또는 실패(Result.failure)를 나타낼 수 있는 Result 객체를 전달합니다. 

3. 직접 테스트해보자 !

다음 코드의 결과를 한번 예상해 볼까요 ? 

결과를 맞춰보세요!

결과는 다음과 같이 "이게" 만 출력되고 "팀이냐고!!"는 출력되지 않습니다. 그 이유는 suspendCoroutine{ } 은 중단점을 지정합니다. 하지만 앞서 살펴본 Continuation의 resume 즉, 일시 중단된 코루틴을 재개할 수 있는 지점이 없기 때문에 runBlocking 코루틴은 일시 중단 상태가 되어 메인 함수의 실행이 종료되지 않습니다.

결과

이 코드의 결과도 한번 맞춰볼까요 ?

여전히 마지막 print( )는 찍히지 않고 있습니다. 이유는 여전히 위 코드와 같은 문제로 일시 중단된 코루틴을 재개할 수 있는 지점이 없기 때문입니다.

 

마지막으로 다음 코드의 결과를 맞춰봅시다.

이번엔 진짜 잘 출력 될까요?

이번엔 마지막 Print( ) 까지 잘 출력되었습니다. 그 이유는 suspendCoroutine의 람다의 파라미터로 continuation 즉, 중단된 코루틴을 다시 재개할 수 있는 객체를 전달받고 이 continuation 객체의 resumeWith( ) 메소드를 활용해 중단된 코루틴을 다시 재개 시켰기 때문에 모든 Print( )가 출력되고, 함수와 코루틴이 모두 종료됩니다.

 

4. 서로 다른 스레드의 suspend <-> resume

코루틴의 suspend, resume은 다른 Thread에서도 가능합니다. 이 코드는 Main Thread에서 runBlocking 코루틴을 생성하고, 스레드 람다 블럭을 사용해 새로운 스레드를 만들었습니다. 그리고 이 스레드에서 1ms 대기 후 중단된 코루틴을 재개했으며 이전 결과와 같은 결과를 출력하는 것을 확인할 수 있습니다.