코틀린 코루틴 (3장 정리)

윤성현·2024년 12월 13일

코틀린 코루틴

목록 보기
3/11
post-thumbnail

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

💡 서론

  • 중단 함수는 코틀린 코루틴의 핵심
  • 코루틴을 중단한다는 것은 실행을 중간에 멈추는 것을 의미
    • 🎮 게임 비유: 체크포인트에서 저장하고 종료한 뒤 다른 일을 하다가 나중에 다시 시작
  • 코루틴은 중단될 때 Continuation 객체를 반환
    • Continuation을 이용해 멈췄던 곳에서 다시 실행 가능

코루틴 vs 스레드 ⚔️

  • 🛑 스레드는 저장 불가, 멈추는 것만 가능
  • 코루틴은 중단 시 자원을 사용하지 않음
  • 코루틴은 다른 스레드에서 시작 가능하며, Continuation 객체는 직렬화와 역직렬화가 가능

🔄 재개

작업이 재개되는 원리를 살펴보자

  • 작업을 재개하려면 코루틴이 필요
  • 중단 함수는 코루틴을 중단할 수 있는 함수를 의미
    ( = 중단 함수가 반드시 코루틴(또는 다른 중단 함수)에 의해 호출되어야 함을 의미)

어떻게 재개시킬 것인가 🤔

suspend fun main() {
	println("Before")
	
	// 중단 함수
	suspendCoroutine<Unit> { }
	
	println("After")
}
// Before 만 출력됨
  • 위 코드를 실행하면 “After”는 출력되지 않고, 코드는 실행된 상태로 유지됨
  • 멈춘 상태로 재개되지 않은 상태
  • Continuation 객체는 어디 있을까?
    suspendCoroutine 함수가 Continuation 객체를 인자로 받음
suspend fun main() {
	println("Before")
	
	suspendCoroutine<Unit> { continuation ->
		println("Before too")
	}
	
	println("After")
}
// Before
// Before too
  • suspendCoroutine 함수는 중단되기 전에 continuation 객체를 사용할 수 있음
  • suspendCoroutine 이 호출된 뒤에는 이미 중단되어 continuation 객체를 사용할 수 없음
suspend fun main() {
	println("Before")
	
	suspendCoroutine<Unit> { continuation ->
		continuation.resume(Unit)
	}
	
	println("After")
}
// Before
// After
  • continuation 객체를 이용해 코루틴을 중단한 후 바로 실행할 수 있음
private val executor = 
	Executors.newSingleThreadScheduledExecutor {
		Thread(it, "scheduler").apply { isDaemon = true }
	}
	
suspend fun delay(timeMillis: Long): Unit =
	suspendCoroutine { cont ->
		executor.schedule({
			cont.resume(Unit)
		}, timeMillis, TimeUnit.MILLISECONDS)
	}
	
suspend fun main() {
	println("Before")
	
	delay(1000)
	
	println("After")
}
// Before
// 1초 뒤
// After
  • delay의 동작 방식 (어떻게 멈추고, 어떻게 다시 주도권을 반환하는지 보여주는 코드)
  • executor가 스레드를 사용하지만, delay 함수를 사용하는 모든 코루틴의 전용 스레드가 되어 대기할 때마다 하나의 스레드를 블로킹하지 않음

🔢 값으로 재개하기

  • suspendCoroutine을 호출할 때 continuation 객체로 반환될 값의 타입을 지정할 수 있음
  • resume을 통해 반환되는 값은 반드시 지정된 타입과 같은 타입이어야 함
suspend fun main() {
    val i: Int = suspendCoroutine<Int> { cont ->
        cont.resume(42)
    }
    println(i) // 42
    
    val str: String = suspendCoroutine<String> { cont -> 
        cont.resume("Some text")
    }
    println(str) // Some text
    
    val b: Boolean = suspendCoroutine<Boolean> { cont ->
        cont.resume(true)
    }
    println(b) // true
}
  • API를 호출해 네트워크 응답을 기다리려고 중단하는 상황은 자주 발생
  • 코루틴이 없다면 스레드는 응답을 기다리고 있게 됨
  • 코루틴을 사용해서 일을 중단하면서 동시에 “데이터를 받고 나서 받은 데이터를 resume 함수를 통해 보내줘” 라고 요청 가능
  • 데이터가 도착하면 스레드는 코루틴이 중단된 지점에서 재개됨
suspend fun requestUser(): User {
	return suspendCoroutine<User> { cont ->
		requestUser {user ->
			cont.resume(user)
		}
	}
}

suspend fun main() {
	println("Before")
	val user = requestUser()
	println(user)
	println("After")
}
  • 중단 함수는 Retrofit, Room 같은 널리 사용되는 라이브러리에 의해 이미 지원되고 있음

🤔 의문?

  • API가 데이터를 넘겨주는 대신 문제가 발생하면 어떻게 될까?
  • 서비스가 종료되거나 에러로 응답이 오면 어떻게 될까?

⇒ 이런 경우 데이터를 반환할 수 없으므로 코루틴이 중단된 곳에서 예외를 발생시켜야 함

⚠️ 예외로 재개하기

  • 일반적인 함수와 같이 suspendCoroutine 함수도 값을 반환하거나 예외를 던짐
  • resumeWithException 이 호출되면 중단된 지점에서 인자로 넣어준 예외를 던짐
// 네크워크 관련 예외 발생 예시 코드
suspend fun requestUser() : User {
    return suspendCancellableCoroutine<User> { cont ->
        requestUser { resp ->
            if (resp.isSuccessful) {
                cont.resume(resp.data)
            } else {
                val e = ApiException(
                    resp.code,
                    resp.message
                )
                cont.resumeWithException(e)
            }
        }
    }
}

suspend fun requestNews(): News {
    return suspendCancellableCoroutine<News> { cont ->
        requestNews(
            onSuccess -> { news -> cont.resume(news) },
            onError -> { e -> cont.resumeWithException(e) }
        )
    }
}
  • 중단 함수는 데이터를 반환하거나, 예외를 던질 수 있음
  • 코루틴의 유연성: 오류 발생 시에도 적절히 처리 가능

함수가 아닌 코루틴을 중단시킨다

중단 함수는 무엇을 중단할까? 🛑

  • 중단 함수는 코루틴이 아니며, 단지 코루틴을 중단할 수 있는 함수
  • 중단 함수는 함수 자체가 아니라, 코루틴의 실행 흐름을 관리하는 역할

0개의 댓글