본문 바로가기

IT/Android

📌 코루틴을 콜백처럼! suspendCancellableCoroutine 제대로 이해하기

728x90
반응형

Android 개발이나 코틀린 백엔드 개발을 하다 보면, 기존의 콜백 기반 비동기 API를 코루틴으로 변환하고 싶은 순간이 자주 옵니다.
이때 자주 사용하는 도구 중 하나가 바로 suspendCancellableCoroutine입니다.

하지만 이 함수는 단순히 suspendCoroutine보다 더 강력하면서도 복잡할 수 있습니다.
이번 글에서는 언제, 왜, 어떻게 써야 하는지를 이해하고, resume, tryResume, completeResume의 차이까지 함께 살펴보겠습니다.


🔍 suspendCancellableCoroutine이란?

기존의 콜백 기반 API를 코루틴의 suspend 함수로 변환하고 싶을 때 사용합니다.
특히 코루틴이 중단(cancel)될 수 있다는 것을 고려해야 한다면, suspendCoroutine이 아닌 suspendCancellableCoroutine을 써야 합니다.

suspend fun getLocation(context: Context): Location =
    suspendCancellableCoroutine { cont ->
        val client = LocationServices.getFusedLocationProviderClient(context)
        client.lastLocation
            .addOnSuccessListener { cont.resume(it) }
            .addOnFailureListener { cont.resumeWithException(it) }
    }

이 예시처럼, 콜백 기반 API를 코루틴 방식으로 감싸기만 하면 suspend 함수처럼 사용 가능합니다.


✅ suspendCoroutine vs suspendCancellableCoroutine

기능suspendCoroutinesuspendCancellableCoroutine
기본 suspend 기능
코루틴 취소 감지
invokeOnCancellation 사용 가능
 

즉, 코루틴이 취소되었을 때 리소스를 안전하게 정리하려면 suspendCancellableCoroutine이 필수입니다.


⚠️ 메인 스레드에서 써도 괜찮을까?

YES, 잘만 쓰면 메인 스레드에서도 안전하게 사용할 수 있습니다.
단, 아래 조건을 지켜야 합니다:

  • 코루틴은 suspend 상태일 뿐 메인 스레드를 블로킹하지 않음
  • 콜백에서 너무 자주 resume하지 않도록 주의
  • invokeOnCancellation을 통해 취소 시 리소스를 정리해야 메모리 누수 방지 가능
cont.invokeOnCancellation {
    someApi.unregisterCallback(callback)
}

 


🎯 resume vs tryResume vs completeResume

✅ resume(value)

  • 즉시 코루틴을 재개
  • 이미 resume된 경우 → ❗예외(IllegalStateException) 발생
cont.resume("OK") // 한 번만 호출되어야 함

✅ tryResume(value)

  • resume을 시도하고, 성공 시 토큰 반환
  • 실패(이미 재개되었거나 취소됨) 시 null 반환 → ❗예외 안 남
val token = cont.tryResume("OK")
if (token != null) {
    cont.completeResume(token)
}

✅ completeResume(token)

  • tryResume()의 결과로 받은 토큰을 사용해 실제로 코루틴을 재개
  • tryResume()과 쌍으로 꼭 사용해야 코루틴이 재개됨

tryResume()만 호출하고 completeResume()을 생략하면 코루틴은 영원히 resume되지 않습니다.


🧠 실전 예제: 콜백 기반 API를 안전하게 감싸기

suspend fun waitForResult(): String = suspendCancellableCoroutine { cont ->
    val callback = object : Callback {
        override fun onSuccess(result: String) {
            cont.tryResume(result)?.let {
                cont.completeResume(it)
            }
        }

        override fun onError(e: Exception) {
            cont.tryResumeWithException(e)?.let {
                cont.completeResume(it)
            }
        }
    }

    api.registerCallback(callback)

    cont.invokeOnCancellation {
        api.unregisterCallback(callback)
    }
}

✅ 정리

상황추천 메서드
resume을 한 번만 호출하는 경우 resume()
여러 경로에서 동시에 resume 가능성 있음 tryResume() + completeResume()
예외 발생 없이 안전하게 resume 시도 tryResume() 사용
코루틴 취소 시 리소스 정리 필요 suspendCancellableCoroutine + invokeOnCancellation
 

📌 마무리

suspendCancellableCoroutine은 단순히 코루틴을 일시 중단하는 함수가 아닙니다.
콜백 기반 코드를 코루틴 스타일로 안전하게 바꾸는 핵심 도구이며,
취소 처리와 resume 관리에 신중해야 합니다.

코루틴을 좀 더 깊이 이해하고자 한다면, 이 개념을 정확히 알고 사용하는 것이 매우 중요합니다.

728x90
반응형