Kotlin Coroutine (3) : 중단 구현

Giyun Kim·2026년 3월 1일

Kotlin Coroutine

목록 보기
3/8

0. 들어가며

저번 글에서 시퀀스 빌더를 다루며 중단이라는 개념을 배워보았다.

중단이 왜 필요한 지 알았으니, 이번 글에서는 중단이 어떻게 구현되는 지를 마르친 모스카와의 Kotlin Coroutine 3장을 읽어보며 알아보도록 한다.

1. 중단은 어떻게 작동할까?

1-1. Coroutine에서의 중단

중단함수란 Kotlin Coroutine의 핵심과 같다.

중단이 가능하다 == Kotlin Coroutine 모든 개념들의 기초

앞서 말했다시피, 중단한다는 것은 실행을 중간에 멈추는 것을 의미한다. 쉽게 말해, 게임을 하다 중간에 일시정지 후 저장한 뒤 추후 다시 불러와 중단 지점부터 다시 진행하는 것이다.

이는 Coroutine의 철학과 일맥상통한다.
Coroutine은 중단 시 Continuation 객체를 반환하는데, 이 객체는 예시로 들었던 게임의 세이브 파일이라고 생각하면 이해하기 쉽다.
Continuation 객체를 활용하면 중단 지점에서부터 다시 Coroutine을 실행할 수 있다.

여기서 스레드와 Coroutine의 차이와 우열이 명확하게 드러난다.

스레드 : 중단만 가능. 저장 불가.
Vs.
Coroutine : 중단 및 저장 가능.

심지어 Coroutine은 중단했을 때 어떤 자원도 소모하지 않는다!
다른 스레드에서 실행될 수도 있고, 직렬/역직렬화가 자유롭ek.

1-2. 재개

중단의 원리를 대략적으로 확인했으니, 재개 방식을 보도록 하자.

아니, 애초에 Coroutine을 어떻게 만드는데?
일단 만들어야 중단을 하든 재개를 하든 하지!

runBlockinglaunch 등이 있는데, 이건 추후에 보도록 하자.

일단 중단 가능한 main 함수를 소개하도록 하겠다.
말 그대로 Coroutine을 중단할 수 있는 함수다.

다만, 알아야 할 것이 있다.

  • 중단 함수는 반드시 Coroutine이나, 다른 중단 함수에 의해 호출되어야 한다.
  • 중단 함수는 중단할 곳이 필요하다

중단할 곳이 무엇일까? main은 일반적인 시작점이므로, Kotlin은 Coroutine 내부에서 suspend 키워드가 붙은 main함수를 실행한다. 아래 예시를 보자.

suspend fun main() {
	println("A")
    
    print("B")
}

두 지점 사이에서 중단한다고 가정하면, Kotlin 라이브러리가 제공하는 suspendCoroutine 함수를 사용한다.

suspend fun main() {
	println("A")
    suspendCoroutine<Unit> { continuation ->
    	println("After A")
    }
    print("B")
}

상기 코드가 실행되면, A까지만 출력되며 해당 main은 실행된 채로 유지된다. 그리고 중단 직전 {} 식 내의 After A가 출력된다.

suspend fun main() {
	println("A")
   suspendCoroutine<Unit> { continuation ->
   	continuation.resume(Unit)
   }
   print("B")
}

이렇게 {} 내에서 곧장 resume을 실행하면 B가 출력된다.

그럼 내가 원할 때 재개되도록 하려면 어떻게 해야 할까?

2. 재개 방식

2-1. 값으로 재개하기

람다 식 내 Unit을 왜 인자로 넣을까? suspendCoroutine의 타입 인자로도 Unit을 사용하는데, 이는 우연의 일치가 아니다.

Unit은 함수의 리턴 타입이면서, Continuation의 제네릭 타입인자이다. 다시 말해, suspendCoroutine을 호출할 때 Continuation 객체로 반환될 타입을 지정할 수 있다는 것이다. 아래 예시를 보자.

suspend fun main() {
	val i : suspendCoroutine<Int> { continuation ->
    	continuation.resume(45)
    }
    print(i) /* 45 */
}

값으로 재개한다는 것이 와닿지 않을 수 있는데, 쉽게 설명하면 이런 것이다.
앱에서 API를 호출해 네트워크 응답을 기다리는 것처럼 특정 데이터를 기다리고자 중단하는 경우는 꽤 많다.

스레드는 특정 데이터가 필요한 시점까지 비즈니스 로직을 수행하다가, 이후 네트워크 라이브러리를 통해 데이터를 요청하게 되는데 이 때 Coroutine이 없다면 스레드는 멈춰선 채 기다릴 수밖에 없게 된다.

스레드는 어떤 OS에서든 생성하는 비용이 크며, 특히나 Android처럼 메인 스레드가 중요하다면 스레드가 가만히 멈춰서서 대기한다는 건 치명적인 문제가 될 수 있다.

Coroutine이 있다면 중단과 동시에 '데이터를 받은 뒤 해당 데이터를 resume 함수를 통해 전달해달라고' Continuation 객체를 매개로 라이브러리 측에 전달할 수 있다.

그런 뒤 스레드는 마음 편하게 본인의 일을 이어갈 수 있는 것이다. 이후 데이터가 도착하면 스레드는 코루틴이 중단되었던 지점부터 다시 이어나갈 수 있다.

실제로 중단 함수는 우리가 Android 개발하며 익숙하게 자주 쓰는 Retrofit, Room 등의 라이브러리에서 이미 지원하고 있다. 그렇기에 우리가 중단 함수 내에서 직접 Callback 함수를 사용하는 일은 거의 없다. 만약 필요하다면 앞서 소개한 suspendCoroutine 대신, 추후 소개할 suspendCancellableCoroutine을 사용하는 편이 좋다.

2-2. 예외로 재개하기

API 호출 후 받은 값으로 재개할 수 있음은 앞서 살펴보았다. 그런데, 우리가 API 호출을 시도할 때 항상 status 200을 받을 수는 없다. 예외가 돌아오는 경우도 심심치 않게 존재하기 마련이다.

다시 말해, 모든 함수는 값이나 예외를 던진다는 것이다.

이런 경우는 어떻게 해야할까? Coroutine에는 resumeWithException이 존재한다. 해당 함수가 호출되면 중단된 지점에서 인자로 넣어준 예외를 던지게 된다.

class MyException : Throwable("Oh no!")

suspen fun requestUser() : User {
	return suspendCancellableCoroutine<User> { cont ->
    	requestUser { response ->
        	if (response.isSuccesful) {
            	cont.resume(response.data)
            } else {
            	val e = ApiException(
                	response.code,
                    response.message
                )
                cont.resumeWithException(e)
            }
        }
    }
}

위 예시 코드를 보면 어떤 식으로 동작하는 지 명확하게 알 수 있다.

3. 중단시키는 대상

이번 장에서 강조하는 것은, 함수가 아닌 Coroutine을 중단시킨다는 것이다. 단, 중단 함수라는 것은 Coroutine이 아니고, 단순히 Coroutine을 중단시킬 수 있는 함수다.

이런 설명을 하는 이유는, 앞서 말했 듯 중단 함수는 반드시 Coroutine이나, 다른 중단 함수에 의해 호출되어야 한다는 점을 강조하고자 함이다.

아래 잘못된 예시를 보자.

var continuation : Continuable<Unit>? = null

suspend fun suspendAndSetContinuation() {
	suspendCoroutine<Unit> { cont ->
    	continuation = cont /* 전역 변수에 cont 저장 */
    }
}

suspend fun main() {
	println("A")
    suspendAndSetContinuation()
    continuation?.resume(Unit)
    
    print("B")
}

다른 스레드나 Coroutine으로 재개한 것이 아니므로 당연히 B는 출력되지 않는다.

4. 마치며

Coroutine이 어떻게 중단, 재개되는지는 어렴풋이 이해했을 것이다. 다음 장에서는 Coroutine이 실제로 어떻게 구현되었는지 살펴볼 예정이다.

profile
Android 개발자가 되기까지

0개의 댓글