중단 함수는 코틀린 코루틴의 핵심이다. 중단이 가능하다는 건 코틀린 코루틴의 다른 모든 개념의 기초가 되는 필수적인 요소다. 이번 장의 목표는 중단이 어떻게 작동하는지 확실하게 이해하는 데 있다.

코루틴을 중단한다는 건 실행을 중간에 멈추는 것을 의미한다. 비디오 게임을 하다가 멈추는 상황이랑 비슷하다. 체크포인트에서 게임을 저장하고 종료한 뒤, 사용자와 컴퓨터는 각각 다른 일에 집중할 수 있다. 나중에 게임을 다시 하고 싶을 때 게임을 재개하고 저장한 체크포인트에서 시작하면 이전에 종료했던 순간부터 게임을 즐길 수 있다. 이는 코루틴의 철학과 비슷하다. 코루틴은 중단되었을 때 Continuation 객체를 반환한다. 이 객체는 게임을 저장하는 것과 비슷하다. Continuation을 이용하면 멈췄던 곳에서 다시 코루틴을 시작할 수 있다.

여기서 코루틴은 스레드와 많이 다른 것을 알 수 있는데, 스레드는 저장이 불가능하고 멈추는 것만 가능하기 때문이다. 이러한 점에서 코루틴이 훨씬 강력한 도구라 할 수 있다. 중단했을 때 코루틴은 어떤 자원도 사용하지 않는다. 코루틴은 다른 스레드에서 시작할 수 있고, 컨티뉴에이션(continuation) 객체는 (이론상) 직렬화와 역직렬화가 가능하며 다시 실행될 수 있다.

재개

작업이 재개되는 원리를 사용 예제를 통해 살펴보자. 작업을 재개하려면 코루틴이 필요하다. 코루틴은 이후에 소개될 runBlocking이나 launch와 같은 코루틴 빌더를 통해 만들 수 있다. 더 간단한 방법도 있지만, 여기서는 중단 가능한 main 함수를 사용한다.

suspend(중단) 함수는 말 그대로 코루틴을 중단할 수 있는 함수다. 이는 suspend 함수가 반드시 코루틴(또는 다른 중단 함수)에 의해 호출되어야 함을 의미한다. 중단 함수는 중단할 수 있는 곳이 필요하다. main 함수는 시작점이기 때문에 코틀린은 코루틴 내부에서 suspend가 붙은 main 함수를 실행한다.

suspend fun main() {
    println("Before")

    println("After")
}

이 예제는 “Before”와 “After”를 출력하는 간단한 프로그램이다. 만약 두 지점 사이에서 중단하면 어떻게 될까? 두 지점 사이를 중단 지점으로 코틀린 라이브러리에서 제공하는 suspendCoroutine 함수를 사용한다.

<aside> 📢 suspendCoroutine 함수는 원시 함수인 suspendCoroutineUninterceptedOrReturn을 곧바로 호출한다. 원시 함수는 라이브러리가 아닌 컴파일러 내부에서 구현된 함수를 의미한다.

</aside>

suspend fun main() {
    println("Before")

    suspendCoroutine<Unit> {  }
    
    println("After")
}

// Before

위 코드를 실행하면 “After”는 출력되지 않으며, 코드는 실행된 상태로 유지된다(main 함수가 끝나지 않았기 때문이다). 코루틴은 “Before” 이후에 중단된다. 프로그램은 멈춘 뒤 재개되지 않는다. 그러면 어떻게 다시 실행시킬 수 있을까? 앞서 언급했던 Continuation은 어디 있을까?

suspendCoroutine이 호출된 지점을 보면 람다 표현식({ })으로 끝났다는 걸 알 수 있다. 인자로 들어간 람다 함수는 중단되기 전에 실행된다. 이 함수는 Continuation 객체를 인자로 받는다.

suspend fun main() {
    println("Before")

    suspendCoroutine<Unit> { continuation ->
        println("Before too")
    }

    println("After")
}

// Before
// Before too

다른 함수를 곧바로 호출하는 함수는 let, apply, useLines처럼 코틀린에서 흔히 볼 수 있다. suspendCoroutine 함수는 이 함수들과 같은 방식으로 설계되어 있어 중단되기 전에 Continuation 객체를 사용할 수 있다. suspend Coroutine이 호출된 뒤에는 이미 중단되어 Continuation 객체를 사용할 수 없기 때문에, 람다 표현식이 suspendCoroutine 함수의 인자로 들어가 중단되기 전에 실행되는 것이다. 람다 함수는 Continuation 객체를 저장한 뒤 코루틴을 다시 실행할 시점을 결정하기 위해 사용된다.

Continuation 객체를 이용해 코루틴을 중단한 후 곧바로 실행할 수 있다.

suspend fun main() {
    println("Before")

    suspendCoroutine<Unit> { continuation ->
        continuation.resume(Unit)
    }

    println("After")
}

// Before
// After

앞 예제에서 “After”가 출력되는 건 suspendCoroutine에서 resume을 호출했기 때문이다.

<aside> 📢 코드만 보면 중단 후 곧바로 실행이 되었다고 생각되지만 실제로는 최적화로 인해 곧바로 재개될 경우 아예 중단되지 않을 수도 있다.

</aside>

<aside> 📢 코틀린 1.3 이후로 Continuation 클래스의 형태가 달라졌다. 원래는 resume과 resumeWithException을 사용했지만, 지금은 Result를 반환하는 resumeWith 함수 하나만 남았다. resume과 resumeWithException 함수는 resumeWith을 사용하는 표준 라이브러리의 확장 함수가 되었다.

</aside>