suspend function: 개념, 구문 및 코루틴에서의 작동

저자: IT Sectr 게시일: 2026-06-22 읽는 시간: 9 분

Suspend function은 suspend 수정자가 있는 함수로, 스레드를 차단하지 않고 실행을 일시 중지했다가 동일한 코루틴에서 나중에 재개할 수 있습니다. JetBrains Kotlin Docs, 2025에 따르면, suspend 함수는 코루틴의 기본 구성 요소로, 콜백 없이 비동기성을 제공합니다. 각 suspend 함수는 Continuation 기반의 상태 머신으로 컴파일되어 일시 중단 지점을 효율적으로 관리할 수 있습니다.

핵심 사항

  • Suspend — 함수를 일시 중단 가능(비동기)으로 표시하는 Kotlin 키워드
  • Continuation — 컴파일러가 상태를 저장하기 위해 모든 suspend 함수에 추가하는 숨겨진 매개변수
  • 일시 중단 지점 — 다른 suspend 함수가 호출되는 위치로, 코루틴이 차단 없이 중단될 수 있음
  • 상태 머신 — suspend 함수의 내부 표현으로, 각 일시 중단 지점이 별도의 상태
  • 코루틴에서만 호출 가능 — suspend 함수는 다른 suspend 함수 또는 launch/async에서만 호출 가능

Kotlin에서 suspend function이란?

Suspend function은 suspend 키워드로 선언된 함수로, 스레드를 차단하지 않고 하나 이상의 지점에서 실행을 일시 중단할 수 있습니다. 다른 suspend 함수 내에서 suspend 함수를 호출할 때마다 잠재적인 일시 중단 지점이 됩니다.

kotlin
suspend fun fetchUserData(): User {
    val response = httpClient.get("/user")
    return parser.parse(response)
}

Kotlin 컴파일러는 이러한 함수를 상태 머신으로 변환합니다. 각 일시 중단 지점(다른 suspend 함수 호출)은 상태(label)가 됩니다. 현재 스레드는 상태 사이에서 해제되고, 대기 중인 작업이 완료되면 다음 상태에서 실행이 재개됩니다.

역사

suspend 함수는 Kotlin 1.3(2018년)에서 코루틴과 함께 실험적 기능으로 등장했으며 Kotlin 1.5(2021년)에서 안정화되었습니다. 그 이전에는 Kotlin/Java에서 비동기 처리가 콜백, RxJava 및 CompletableFuture를 통해 이루어졌습니다. suspend 함수는 선형 구문과 자동 스레드 관리를 갖춘 대안을 제공했습니다.

suspend 함수의 작동 방식: Continuation과 상태 머신

suspend 함수의 내부 작동 방식을 이해하는 것이 코루틴을 올바르게 다루는 핵심입니다. 일반 함수와 달리, 각 suspend 함수는 Continuation 인터페이스를 가진 클래스로 컴파일됩니다.

Continuation — 숨겨진 매개변수

Kotlin 컴파일러는 각 suspend 함수의 매개변수 목록 끝에 Continuation 타입의 매개변수를 추가합니다. Continuation은 다음을 포함합니다:

  • context — CoroutineContext(디스패처, job, 컨텍스트 요소)
  • resumeWith — 결과 또는 예외로 실행을 재개하는 메서드
  • label — 상태 머신에서 현재 상태의 인덱스

상태 머신 예제

다른 suspend 함수에 대한 두 번의 호출이 있는 suspend 함수가 있다고 가정해 봅시다:

kotlin
suspend fun process() {
    val a = stepOne()
    val b = stepTwo(a)
    println(b)
}

컴파일러는 이를 레이블이 있는 상태 머신으로 변환합니다:

kotlin
// Simplified generated code representation
fun process(cont: Continuation<Unit>): Any? {
    val cont = cont as ProcessContinuation
    when (cont.label) {
        0 -> {
            cont.label = 1
            if (stepOne(cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
        }
        1 -> {
            cont.label = 2
            val a = cont.result as TypeA
            if (stepTwo(a, cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
        }
        2 -> {
            println(cont.result)
            Unit
        }
    }
}

핵심 관찰: 함수가 COROUTINE_SUSPENDED를 반환하면 현재 스레드가 해제됩니다. 비동기 작업이 완료되면 Continuation.resumeWith가 호출되고 상태 머신은 다음 레이블부터 계속됩니다.

suspend 함수 구문: 선언 및 호출

suspend 함수 선언은 fun 앞의 suspend 키워드를 제외하면 일반 함수와 다르지 않습니다. 제한 사항은 단 하나입니다: suspend 함수는 코루틴이나 다른 suspend 함수에서만 호출할 수 있습니다.

기본 선언

kotlin
suspend fun delayAndReturn(ms: Long): String {
    delay(ms)
    return "Done after ${ms}ms"
}

이 예제에서 delay도 suspend 함수로, 스레드를 차단하지 않고 지정된 밀리초 동안 코루틴을 일시 중단합니다. 지연 후 실행이 재개됩니다.

코루틴에서 호출

kotlin
fun main() = runBlocking {
    val result = delayAndReturn(1000)
    println(result)
}

runBlocking은 일반 세계와 코루틴 사이의 브리지를 만듭니다. 람다 내부에서는 모든 suspend 함수를 호출할 수 있습니다.

suspend 람다와 함수형 타입

Kotlin은 함수형 타입의 suspend 버전을 지원합니다 — suspend () -> Tsuspend (A) -> B. 이를 통해 고차 함수에 비동기 람다를 전달할 수 있습니다:

kotlin
suspend fun  withRetry(
    retries: Int = 3,
    block: suspend () -> T
): T {
    repeat(retries - 1) {
        try { return block() }
        catch (_: Exception) { delay(100) }
    }
    return block()
}

withRetry 함수는 suspend 람다를 받아 오류 시 실행을 재시도합니다. 이는 재시도가 있는 네트워크 요청의 일반적인 패턴입니다.

suspend 함수와 일반 함수의 차이점

suspend 함수와 일반 함수의 차이점은 단순히 수정자를 추가하는 것 이상입니다. 주요 차이점을 살펴보겠습니다.

특성일반 함수suspend 함수
실행 스레드완료까지 스레드 차단스레드 해제 후 재개 가능
컴파일러 매개변수지정된 매개변수만끝에 암시적 Continuation
일반 함수에서 호출가능불가능
스택물리적 스레드 스택힙의 상태 머신 + 지점 간 물리적 스택
반환값직접 값값 또는 COROUTINE_SUSPENDED
성능최소 오버헤드상태 머신당 ~수 나노초(Kotlin 1.9+)

suspend 함수를 일반 함수에서 호출할 수 없는 이유

일반 함수에는 Continuation이 없습니다 — 상태를 저장할 곳도, 실행을 재개할 수단도 없습니다. 일반 함수에서 suspend 함수를 호출해야 하는 경우 runBlocking(테스트용) 또는 CoroutineScope.launch(생명주기를 고려한 프로덕션용)를 사용하세요.

Android에서의 suspend 함수 예제

Kotlin을 사용한 Android 앱에서 suspend 함수의 세 가지 실제 시나리오를 살펴보겠습니다.

예제 1: suspend 쿼리를 사용하는 Room DAO

Room은 suspend 함수를 직접 지원합니다 — 쿼리가 자동으로 백그라운드 스레드에서 실행됩니다:

kotlin
@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :id")
    suspend fun getUser(id: Int): User?

    @Insert
    suspend fun insertUser(user: User)
}

Room은 내부적으로 쿼리 실행을 위해 Dispatchers.IO를 사용하며, 결과는 suspend 함수가 호출된 디스패처에서 반환됩니다.

예제 2: 화면 로딩을 위한 suspend 함수 구성

kotlin
class ProfileViewModel : ViewModel() {
    private val repo = ProfileRepository()

    fun loadProfile(id: String) {
        viewModelScope.launch {
            val profile = repo.getProfile(id)
            _profile.value = profile
        }
    }
}

ViewModelScope.launch는 코루틴을 생성하고, 그 내부에서 getProfile suspend 함수가 호출됩니다. 결과를 받은 후 UI가 메인 스레드에서 업데이트됩니다.

예제 3: 순차적 비동기 단계

kotlin
suspend fun placeOrder(cart: Cart): OrderResult {
    val validated = validateCart(cart)
    val payment = processPayment(validated)
    val receipt = sendReceipt(payment)
    return receipt
}

세 개의 suspend 함수가 순차적으로 실행됩니다. 각 단계에서 코루틴은 스레드를 차단하지 않고 일시 중단될 수 있습니다. 어떤 단계에서 예외가 발생하면 나머지 단계는 실행되지 않아 잘못된 주문 상태를 방지합니다.

suspend 함수 사용 시 흔한 실수

숙련된 Kotlin 개발자도 suspend 함수를 설계할 때 실수를 합니다. 가장 흔한 실수들을 살펴보겠습니다.

실수 1: suspend 내에서 차단 호출 사용

suspend 함수가 코드를 자동으로 비동기로 만들지는 않습니다. Thread.sleep(), InputStream.read() 및 기타 차단 호출은 여전히 스레드를 차단합니다. 차단 작업을 감싸려면 withContext(Dispatchers.IO)를 사용하세요.

실수 2: 불필요한 suspend 함수 생성

함수가 다른 suspend 함수를 호출하지 않고 비동기 작업을 수행하지 않는 경우 — suspend 수정자는 불필요합니다. 상태 머신에 오버헤드를 추가하고 호출 컨텍스트를 제한합니다. 함수가 실제로 일시 중단될 때만 suspend로 만드세요.

실수 3: CancellationException 무시

코루틴이 취소되면 suspend 함수는 CancellationException을 던집니다. 생각 없이 잡지 마세요 — 호출 코드가 취소를 올바르게 완료할 능력을 빼앗게 됩니다. 마무리 작업을 수행해야 하는 경우 finally 블록과 NonCancellable을 사용하세요.

kotlin
suspend fun safeOperation() {
    try {
        doWork()
    } finally {
        withContext(NonCancellable) {
            cleanup()
        }
    }
}

finally 블록은 취소 시를 포함하여 항상 실행되며, NonCancellable은 정리가 중단되지 않도록 보장합니다.

실수 4: 콜백에서 suspend 함수 호출

코루틴을 생성하지 않고 콜백에서 직접 suspend 함수를 호출할 수 없습니다. 콜백 스타일을 코루틴에 적용하려면 suspendCoroutine 또는 suspendCancellableCoroutine을 사용하세요.

자주 묻는 질문

suspend 함수에 일시 중단 지점이 없어도 되나요?

네, 기술적으로 suspend 함수는 다른 suspend 함수를 호출하지 않을 수 있습니다. 컴파일러는 단일 상태(label 0)로 상태 머신을 생성합니다. 그러나 이러한 함수에는 실용적인 이점이 없습니다 — 일반 함수처럼 실행되지만 오버헤드가 있습니다. 불필요하게 suspend를 사용하지 마세요.

suspend 함수를 디버그하는 방법은?

Kotlin은 kotlinx-coroutines-debug를 제공합니다 — DebugProbes 및 코루틴 추적 도구가 포함된 라이브러리입니다. Arctic Fox부터 Android Studio의 디버거에는 활성 코루틴, 상태 및 일시 중단 지점을 보여주는 Coroutines 탭이 내장되어 있습니다.

일시 중단 지점의 수가 성능에 영향을 미치나요?

각 일시 중단 지점은 상태 머신에 새 상태를 만듭니다. 대부분의 응용 프로그램에서 한 지점의 오버헤드는 수 나노초입니다(Kotlin 1.9+). 루프에서 수만 개의 지점이 있는 경우에만 작업 결합 또는 sequence/flow 사용을 고려해야 합니다.

suspend 함수는 다른 언어의 async/await와 어떻게 다른가요?

Kotlin에서 suspend는 함수형 수정자이며 반환값 마커(C#의 async)가 아닙니다. 모든 suspend 함수는 모든 매개변수와 반환 타입을 가질 수 있으며, 호출은 구문적으로 일반 함수 호출과 다르지 않습니다 — 호출 지점에 await 연산자가 없습니다.

콜백 함수를 suspend로 변환하는 방법은?

적응을 위해 suspendCancellableCoroutine을 사용하세요. 내부에서 continuation.resume()을 호출하는 콜백을 등록하고, 콜백이 구독 취소를 지원하는 경우 취소 토큰을 반환합니다. 이는 오래된 Android API를 래핑하는 표준 패턴입니다.

요약

  • Suspend function — Continuation 메커니즘을 통해 스레드를 차단하지 않고 실행을 일시 중단할 수 있는 suspend 수정자가 있는 함수
  • 상태 머신 — Kotlin 바이트코드에서 suspend 함수의 내부 표현으로, 각 일시 중단 지점은 레이블이 있는 별도의 상태
  • Continuation — 코루틴 컨텍스트와 실행 재개를 위한 resumeWith 메서드를 포함하는 숨겨진 매개변수
  • 코루틴에서만 호출 가능 — suspend 함수는 runBlocking 또는 CoroutineScope 없이 일반 함수에서 접근 불가
  • 차단 작업 suspend 내에서 withContext(Dispatchers.IO) 필요 — 그렇지 않으면 스레드 차단
  • Room 및 Retrofit suspend 함수를 기본적으로 지원하며 백그라운드 스레드를 자동 관리
  • CancellationException — finally + NonCancellable로 취소 처리, CancellationException을 생각 없이 잡지 말 것

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기