1. 일시 중단 함수(Suspending Function)란?
- 일시 중단 함수는 코틀린 코루틴의 핵심 구성 요소로,
suspend키워드로 선언된 함수입니다. - 이 함수들은 실행 중간에 일시 중단되었다가 나중에 다시 재개될 수 있는 특별한 함수입니다.
- 일반 함수와 달리, 실행을 멈추고 스레드를 차단하지 않고 반환할 수 있어 비동기 프로그래밍에 이상적입니다.
기본 구문
suspend fun doSomething() {
// 시간이 걸리는 작업
}
1.1 일시 중단 함수와 일반 함수의 차이
- 일반 함수는 실행이 시작되면 완료될 때까지 해당 스레드를 독점적으로 사용합니다.
- 즉, 함수가 긴 작업을 수행하는 경우 그 스레드는 다른 일을 할 수 없습니다.
- 일시 중단 함수는 특정 지점(suspend 지점)에서 실행을 일시적으로 멈추고, 사용하던 스레드를 반환할 수 있습니다.
- 이 스레드는 다른 작업을 처리하다가, 중단된 작업이 계속될 준비가 되면 같은 스레드나 다른 스레드에서 작업을 재개합니다.
- 일시 중단 함수는 반드시 코루틴 스코프 내부나 다른 일시 중단 함수 안에서만 호출할 수 있습니다.
- 일반 함수에서는 직접 호출할 수 없습니다.
- 컴파일러는 일시 중단 함수를 '상태 머신'으로 변환합니다.
- 이 상태 머신은 함수가 중단된 위치를 기억했다가, 나중에 정확히 그 지점부터 실행을 재개할 수 있게 해줍니다.
// 일반 함수
fun regularFunction() {
// 장시간 실행되는 작업 - 완료될 때까지 스레드 차단
}
// 일시 중단 함수
suspend fun suspendingFunction() {
// 장시간 실행되는 작업 - 스레드를 차단하지 않고 일시 중단 가능
}
// 사용 예시
fun main() = runBlocking {
suspendingFunction() // 코루 틴 내에서 호출 가능
}
// 컴파일 오류 - 일시 중단 함수는 코루틴 외부에서 직접 호출할 수 없음
// fun main() {
// suspendingFunction()
// }
2. 코루틴과 일시 중단 함수의 관계
- 일시 중단 함수는 코루틴 시스템의 기반이 되는 핵심 요소입니다.
- 코루틴은 일시 중단 함수를 실행하는 실행 단위이며, 일시 중단 함수는 코루틴의 실행을 일시 중단시키는 메커니즘을 제공합니다.
2.1 상호 의존성
- 일시 중단 함수는 코루틴 내에서만 호출될 수 있습니다.
- 코루틴은 일시 중단 함수를 통해 비차단 방식으로 일시 중단되고 재개됩니다.
- 이 둘의 관계는 다음과 같이 요약할 수 있습니다:
- 코루틴은 일시 중단 함수를 실행하는 컨테이너입니다.
- 일시 중단 함수는 코루틴이 효율적으로 일시 중단되고 재개될 수 있도록 하는 메커니즘입니다.
// 코루틴과 일시 중단 함수의 관계 예시
fun main() = runBlocking {
// 여기서 runBlocking은 코루틴을 시작하는 빌더입니다
println("코루틴 시작")
delay(1000) // 일시 중단 함수 - 코루틴을 일시 중단합니다
println("코루틴 재개")
}
2.2 코루틴 빌더와 일시 중단 함수
- 코루틴 빌더(
launch,async,runBlocking등)는 코루틴을 생성하고 일시 중단 함수를 실행하는 진입점입니다. - 이 빌더들은 일시 중단 함수가 실행될 수 있는 코루틴 컨텍스트를 제공합니다.
fun main() = runBlocking {
// launch는 새 코루틴을 시작하는 빌더
val job = launch {
delay(1000) // 일시 중단 함수
println("코루틴 내부 작업 완 료")
}
// async는 결과를 반환하는 코루틴을 시작하는 빌더
val deferred = async {
delay(2000) // 일시 중단 함수
"작업 결과"
}
// await는 일시 중단 함수
val result = deferred.await()
println("결과: $result")
}
3. 일시 중단 함수의 내부 동작 원리
- 일시 중단 함수는 컴파일 시점에 특별한 변환을 거쳐 상태 머신으로 변환됩니다.
- 이 변환 과정은 컨티뉴에이션 패싱 스타일(Continuation-Passing Style, CPS) 변환과 유사합니다.
3.1 컨티뉴에이션(Continuation)
- 컨티뉴에이션은 코루틴이 일시 중단된 후 어디서부터 다시 실행해야 하는지에 대한 정보를 담고 있습니다.
- 모든 일시 중단 함수는 마지막 파라미터로
Continuation객체를 암시적으로 받습니다.
// 컴파일러는 다음과 같이 변환합니다:
suspend fun myFunction(param: Type): Result
// 위 함수는 다음과 유사한 형태로 변환됩니다:
fun myFunction(param: Type, continuation: Continuation<Result>): Any
3.2 상태 머신(State Machine)
- 일시 중단 함수는 여러 상태를 가진 상태 머신으로 변환됩니다.
- 각 상태는 일시 중단 지점을 나타내며, 함수가 재개될 때 어느 지점부터 실행해야 하는지 결정합니다.
suspend fun complexFunction() {
println("상태 0")
delay(1000) // 일시 중단 지점 1
println("상태 1")
delay(1000) // 일시 중단 지점 2
println("상태 2")
}
// 위 함수는 대략 다음과 같이 변환됩니다(단순화된 형태):
fun complexFunction(continuation: Continuation<Unit>): Any {
val state = continuation.state
when (state) {
0 -> {
println("상태 0")
return delay(1000, ContinuationImpl(1))
}
1 -> {
println("상태 1")
return delay(1000, ContinuationImpl(2))
}
2 -> {
println("상태 2")
return Unit
}
}
}
참고
실제 변환은 이보다 훨씬 복잡하지만, 기본 아이디어는 함수가 여러 진입점을 가진 상태 머신으로 변환된다는 것입니다.
4. 일시 중단 함수의 활용 패턴
4.1 순차적 실행
- 일시 중단 함수는 비동기 작업을 순차적으로 실행하는 데 이상적입니다.
- 코드는 동기식처럼 보이지만 실제로는 비동기적으로 실행됩니다.
suspend fun fetchUserAndPosts(): Pair<User, List<Post>> {
val user = fetchUser() // 첫 번째 API 호출
val posts = fetchPosts(user.id) // 두 번째 API 호출 (첫 번째 결과에 의존)
return user to posts
}
// 사용 예시
suspend fun displayUserData() {
val (user, posts) = fetchUserAndPosts()
displayUser(user)
displayPosts(posts)
}
4.2 병렬 실행
async와await를 사용하여 여러 일시 중단 함수를 병렬로 실행할 수 있습니다.
suspend fun fetchUserAndPostsConcurrently(): Pair<User, List<Post>> = coroutineScope {
val userDeferred = async { fetchUser() }
val postsDeferred = async { fetchPosts("temp_id") } // 병렬로 시작
val user = userDeferred.await()
val posts = postsDeferred.await()
user to posts
}
4.3 오류 처리
- 일시 중단 함수에서 발생한 예외는 일반 함수와 동일한 방식으로 처리할 수 있습니다.
try-catch블록이나 고차 함수를 사용하여 예외를 처리할 수 있습니다.
suspend fun fetchUserSafely(): User? {
return try {
fetchUser()
} catch (e: Exception) {
println("사용자 정보를 가져오는 중 오류 발생: ${e.message}")
null
}
}
// 고차 함수 활용
suspend fun <T> runSafely(block: suspend () -> T): Result<T> {
return try {
Result.success(block())
} catch (e: Exception) {
Result.failure(e)
}
}
// 사용 예시
suspend fun fetchData() {
val userResult = runSafely { fetchUser() }
userResult.onSuccess { user ->
println("사용자: $user")
}.onFailure { error ->
println("오류: $error")
}
}
5. 일시 중단 함수 만들기
5.1 기본 일시 중단 함수 작성
suspend키워드를 사용하여 함수를 일시 중단 함수로 선언합니다.- 내부에서 다른 일시 중단 함수를 호출할 수 있습니다.
suspend fun myCustomSuspendFunction() {
// 다른 일시 중단 함수 호출
delay(1000)
// 일반 함수 호출
regularFunction()
// 추가 작업
}