[Coroutine] 쀑단(suspend)의 μž‘λ™ 방식

 πŸ’‘ μ½”λ£¨ν‹΄μ—μ„œ 쀑단이 μ–΄λ–»κ²Œ λ™μž‘ν•˜λŠ”μ§€ ν•™μŠ΅ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

 

쀑단은 μ–΄λ–»κ²Œ μž‘λ™ν• κΉŒ?

  • 쀑단 ν•¨μˆ˜λŠ” μ½”ν‹€λ¦° μ½”λ£¨ν‹΄μ˜ 핡심이 λ©λ‹ˆλ‹€.
  • 코루틴을 μ€‘λ‹¨ν•œλ‹€λŠ” 것은 싀행을 쀑간에 λ©ˆμΆ”λŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€.
    • μ²΄ν¬ν¬μΈνŠΈμ— κ²Œμž„μ„ μ €μž₯ν•˜κ³  λ‹€λ₯Έ κ²Œμž„μ„ λ™μž‘ν•˜λŠ” 것과 κ°™μœΌλ©°, μ‚¬μš©μžμ™€ μ»΄ν“¨ν„°λŠ” 각각 λ‹€λ₯Έ 일에 집쀑할 수 μžˆμŠ΅λ‹ˆλ‹€.
    • κ²Œμž„μ„ μž¬κ°œν•  경우 μ €μž₯ν•œ μ²΄ν¬ν¬μΈν„°μ—μ„œ μ‹€ν–‰ν•œλ‹€λ©΄ 이전에 μ’…λ£Œν–ˆλ˜ μˆœκ°„λΆ€ν„° κ²Œμž„μ„ 즐길 수 μžˆμŠ΅λ‹ˆλ‹€.
  • 코루틴은 μ€‘λ‹¨λ˜μ—ˆμ„ λ•Œ Continuation 객체λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

μŠ€λ ˆλ“œμ™€μ˜ 차이점은?

  • μŠ€λ ˆλ“œλŠ” μ €μž₯이 λΆˆκ°€λŠ₯ν•˜κ³ , λ©ˆμΆ”λŠ” κ²ƒλ§Œ κ°€λŠ₯ν•©λ‹ˆλ‹€.
  • 코루틴이 Continuation 객체λ₯Ό λ°˜ν™˜ν•˜μ—¬ λ©ˆμ·„λ˜ κ³³μ—μ„œ λ‹€μ‹œ 코루틴을 μ‹œμž‘ν•˜μ§€λ§Œ μŠ€λ ˆλ“œλŠ” μ €μž₯이 λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.
  • 코루틴은 λ˜ν•œ μ€‘λ‹¨ν–ˆμ„ λ•Œ μ–΄λ–€ μžμ›λ„ μ‚¬μš©ν•˜μ§€ μ•ŠμœΌλ©°, λ‹€λ₯Έ μŠ€λ ˆλ“œμ—μ„œ μ‹œμž‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ»¨ν‹°λ‰΄μ—μ΄μ…˜ κ°μ²΄λŠ” 직렬화와 역직렬화가 κ°€λŠ₯ν•˜λ©° λ‹€μ‹œ 싀행될 수 μžˆμŠ΅λ‹ˆλ‹€.

재개

  • μž‘μ—…μ„ μž¬κ°œν•˜λ €λ©΄ 코루틴이 ν•„μš”ν•˜λ©°, runblockingμ΄λ‚˜ launch와 같은 코루틴 λΉŒλ”λ₯Ό ν™œμš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • 쀑단 ν•¨μˆ˜λŠ” suspend ν‚€μ›Œλ“œλ₯Ό ν™œμš©ν•˜λ©°, λ°˜λ“œμ‹œ 코루틴에 μ˜ν•΄ ν˜ΈμΆœλ˜μ–΄μ•Ό 함을 μ˜λ―Έν•©λ‹ˆλ‹€.
suspend fun main() {
    println("Before")
    println("After")
}
// Before
// After
  • λ§Œμ•½ 두 지점 사이λ₯Ό μ€‘λ‹¨ν•˜κ³  μ‹Άλ‹€λ©΄ suspendCoroutine ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
suspend fun main() {
    println("Before")
    suspendCoroutine<Unit> { continuation ->
        println("Before too")
    }
    println("After")
}
// Before
// Before too
  • main ν•¨μˆ˜λŠ” λλ‚˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ—, AfterλŠ” 좜λ ₯λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • suspendCoroutine ν•¨μˆ˜λŠ” 이 ν•¨μˆ˜λ“€κ³Ό 같은 λ°©μ‹μœΌλ‘œ μ„€κ³„λ˜μ–΄ μžˆμ–΄ μ€‘λ‹¨λ˜κΈ° 전에 μ»¨ν‹°λ‰΄μ—μ΄μ…˜ 객체λ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
continuation.resume(Unit) // Kotlin 1.3 이전
continuation.resumeWith(Unit) // Kotlin 1.3 이후
  • μœ„ μ½”λ“œλ₯Ό suspendCoroutine λ‚΄λΆ€μ—μ„œ μ‹€ν–‰ν•œλ‹€λ©΄, μ€‘λ‹¨λœ 코루틴을 λ°”λ‘œ μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • ν˜„μž¬ resumeκ³Ό resumeWithException ν•¨μˆ˜λŠ” resumeWith을 μ‚¬μš©ν•˜λŠ” ν‘œμ€€ 라이브러리의 ν™•μž₯ ν•¨μˆ˜κ°€ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

μ •μ§€λœ λ’€ λ‹€λ₯Έ μŠ€λ ˆλ“œ μ‹€ν–‰

suspend fun main() {
    println("Before")
    suspendCoroutine<Unit> { continuation ->
        thread {
            println("Suspended")
            Thread.sleep(1000)
            continuation.resume(Unit)
            println("Resumed")
        }
    }
    println("After")
}
// Before
// Suspended
// ... 1초 ν›„
// After
// Resumed
  • μ‹€μ œλ‘œλŠ” 쀑단 된 ν›„ κ³§λ°”λ‘œ μ‹€ν–‰λ˜λŠ” 것이 μ•„λ‹Œ, μ΅œμ ν™”λ‘œ 인해 재개될 경우 μ•„μ˜ˆ μ€‘λ‹¨λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • ν•˜μ§€λ§Œ λ§Œλ“€μ–΄μ§„ λ‹€μŒ 1초 뒀에 μ‚¬λΌμ§€λŠ” μŠ€λ ˆλ“œλŠ” λΆˆν•„μš”ν•΄ 보이며, μŠ€λ ˆλ“œλ₯Ό μƒμ„±ν•˜λŠ” λΉ„μš©μ„ μƒκ°ν–ˆμ„ λ•Œ 더 쒋은 방법을 λͺ¨μƒ‰ν•΄μ•Ό ν•©λ‹ˆλ‹€.

coroutine delay κ΅¬ν˜„

private val excutor =
    Executors.newSingleThreadScheduleExecutor {
        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
  • excutorμ—μ„œ μŠ€λ ˆλ“œλ₯Ό ν™œμš©ν•˜μ§€λ§Œ, delay ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λŠ” λͺ¨λ“  μ½”λ£¨ν‹΄μ˜ μ „μš© μŠ€λ ˆλ“œμ΄λ©°, λŒ€κΈ°ν•  λ•Œλ§ˆλ‹€ ν•˜λ‚˜μ˜ μŠ€λ ˆλ“œλ₯Ό λΈ”λ‘œν‚Ήν•˜λŠ” 방법보닀 훨씬 μ’‹μŠ΅λ‹ˆλ‹€.
  • μ‹€μ œ 코루틴 λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œμ˜ delay와 κ΅¬ν˜„ 방식이 μ •ν™•νžˆ μΌμΉ˜ν•˜λ©°, ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ delayλ₯Ό ꡬ체화 ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

κ°’μœΌλ‘œ μž¬κ°œν•˜κΈ°

  • resume ν•¨μˆ˜λŠ” Unit을 인자둜 λ„£λŠ”λ°, κ·Έ μ΄μœ λŠ” ν•¨μˆ˜μ˜ 리턴 νƒ€μž…μ„ λͺ…μ‹œν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.
  • Unit은 Continuation의 μ œλ„€λ¦­ νƒ€μž… μΈμžλ‘œλ„ ν™œμš©λ©λ‹ˆλ‹€.
val ret: Unit =
    suspendCoroutine<Unit> { cont: Continuation<Unit> ->
        cont.resume(Unit)
    }

val i: Int = suspendCoroutine<Int> { cont ->
    cont.resume(42)
}
  • μœ„μ™€ 같이 νƒ€μž…μ„ μ§€μ •ν•˜λŠ” 것도 κ°€λŠ₯ν•˜λ©°, νƒ€μž…μ„ μ§€μ •ν•˜λŠ” κ²½μš°λŠ” resume을 톡해 λ°˜ν™˜λ˜λŠ” νƒ€μž…μ΄ 지정 νƒ€μž…κ³Ό λ°˜λ“œμ‹œ κ°™μ•„μ•Ό ν•©λ‹ˆλ‹€.
  • κ°’μœΌλ‘œ μž¬κ°œν•˜λŠ” 상황은 μ§€μ •ν•œ κ³³μ—μ„œ μž¬κ°œν•  λ•Œ κΈ°μ‘΄κ³Ό λ‹€λ₯Έ 무엇인가λ₯Ό μΆ”κ°€ν•˜λŠ” 것은 μ–΄λ ΅μŠ΅λ‹ˆλ‹€.
  • ν•˜μ§€λ§Œ μ½”λ£¨ν‹΄μ—μ„œλŠ” κ°’μœΌλ‘œ μž¬κ°œν•˜λŠ” 것을 μžμ—°μŠ€λŸ½κ²Œ μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ½”λ£¨ν‹΄μ˜ κ°’μœΌλ‘œ μž¬κ°œν•˜κΈ°

  • APIλ₯Ό ν˜ΈμΆœν•΄ λ„€νŠΈμ›Œν¬ 응닡을 κΈ°λ‹€λ¦¬λŠ” λ“±μ˜ νŠΉμ • 데이터λ₯Ό μœ„ν•œ 쀑단은 자주 λ°œμƒν•©λ‹ˆλ‹€.
  • μ΄λŸ¬ν•œ μƒν™©μ—μ„œ 코루틴이 μ—†λ‹€λ©΄ μŠ€λ ˆλ“œλŠ” 응닡을 κΈ°λ‹€λ¦¬λŠ” μƒν™©λ§Œ κ°€λŠ₯ν•΄μ§‘λ‹ˆλ‹€.
  • μŠ€λ ˆλ“œλ₯Ό μƒμ„±ν•˜λŠ” λΉ„μš©μ΄ 많이 λ“€λ©°, μ•ˆλ“œλ‘œμ΄λ“œμ²˜λŸΌ 메인 μŠ€λ ˆλ“œκ°€ μ€‘μš”ν•˜κ²Œ μ μš©ν•˜λŠ” 경우 맀우 큰 λ‚­λΉ„λ‘œ μ΄μ–΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.
  • “데이터λ₯Ό λ°›κ³  λ‚˜λ©΄, 받은 데이터λ₯Ό resume으둜 전솑해라”와 같은 Continuation 객체λ₯Ό 톡해 λΌμ΄λΈŒλŸ¬λ¦¬μ— μ „λ‹¬ν•œλ‹€λ©΄, μŠ€λ ˆλ“œλŠ” 쀑간에 λ‹€λ₯Έ 일을 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • κ·Έ ν›„ λ°μ΄ν„°κ°€ λ„μ°©ν•˜λ©΄ μŠ€λ ˆλ“œλŠ” 코루틴이 μ€‘λ‹¨λœ μ§€μ μ—μ„œ μž¬κ°œν•  수 μžˆμŠ΅λ‹ˆλ‹€.
suspend fun main() {
    println("Before")
    val user = suspendCoroutine<User> { cont ->
        requestUser { user ->
            cont.resume(user)
        }
    }
    println(user)
    println("After")
}
// Before
// 데이터λ₯Ό λ°›λŠ” μ‹œκ°„ κ²½κ³Ό
// User(name = Test)
// After
  • μœ„ 경우 νŠΉμ • 데이터λ₯Ό 얻을 λ•ŒκΉŒμ§€ μ€‘λ‹¨λ˜λŠ” 상황을 얻을 수 있으며, μŠ€λ ˆλ“œκ°€ 응닡을 기닀리지 μ•Šκ³ , λ‹€λ₯ΈμΌμ„ μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • user 객체가 λ„μ°©ν•œλ‹€λ©΄, μŠ€λ ˆλ“œλŠ” 코루틴이 μ€‘λ‹¨λœ μ‹œμ μ—μ„œ μž¬κ°œλ˜μ–΄ μ‚¬μš©μžκ°€ μ›ν•˜λŠ” 값을 κΈ°λŒ€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ˜ˆμ™Έλ‘œ μž¬κ°œν•˜κΈ°

  • λ§Œμ•½ APIκ°€ 데이터λ₯Ό λ„˜κ²¨μ£ΌλŠ” κ³Όμ •μ—μ„œ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€λ©΄, 데이터λ₯Ό λ°˜ν™˜ν•  수 μ—†μœΌλ―€λ‘œ 코루틴이 μ€‘λ‹¨λœ κ³³μ—μ„œ μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œμΌœμ•Ό ν•©λ‹ˆλ‹€.
class MyException : Throwable("custom exception")

suspend fun main() {
    try {
        suspendCoroutine<Unit> { cont ->
            cont.resumeWithException(MyException())
        }
    } catch (e: MyException) {
        println("${e.message}")
    }
}

ν•¨μˆ˜κ°€ μ•„λ‹Œ 코루틴을 쀑단

  • 쀑단 ν•¨μˆ˜λŠ” 코루틴이 μ•„λ‹Œ, 단지 코루틴을 쀑단할 수 μžˆλŠ” ν•¨μˆ˜λΌ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
var continuation: Continuation<Unit>? = null

suspend fun suspendAndSetContinuation() {
    suspendCoroutine<Unit> { cont ->
        continuation = cont
    }
}

suspend fun main(){
    println("Before")
    suspendAndSetContinuation()
    continuation?.resume(Unit)
    println("After")
}
// Before
  • μœ„ μ½”λ“œλŠ” 잘λͺ» 된 κ΅¬ν˜„μ„ μ˜ˆμ‹œλ‘œ λ“€κ³  μžˆμŠ΅λ‹ˆλ‹€.
  • μ˜λ„μ™€ 달리 μ’…λ£Œλ˜μ§€ μ•ŠμœΌλ©°, resume이 ν˜ΈμΆœλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.
  • κ·Έ μ΄μœ λŠ” suspendAndSetContinuation()λ₯Ό 톡해 쀑단 지점을 μƒμ„±ν•˜κ³  코루틴을 μΌμ‹œ 쀑단 ν•˜μ˜€μ§€λ§Œ, continuation이 λ‹€μ‹œ 호좜되기 μ „κΉŒμ§€ 코루틴이 μž¬κ°œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • continuation?.resume(Unit)λ₯Ό ν†΅ν•΄μ„œ 코루틴을 μž¬κ°œν•˜μ§€λ§Œ, 이 μ½”λ“œλŠ” 아직 main ν•¨μˆ˜μ˜ μΌμ‹œ μ€‘λ‹¨λœ μ§€μ μ—μ„œ μž¬κ°œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.
  • main ν•¨μˆ˜κ°€ λΉ„λ™κΈ°μ μœΌλ‘œ λ™μž‘ν•˜κ³ , continuation?.resume(Unit)이 호좜되기 전에 ν”„λ‘œκ·Έλž¨μ΄ μ’…λ£Œλ˜μ—ˆκΈ° λ•Œλ¬Έμ΄λ©°, resume()의 ν˜ΈμΆœμ€ main ν•¨μˆ˜κ°€ μ’…λ£Œλ˜κΈ° 전에 μ™„λ£Œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.
  • μ΄λŠ” λ©”λͺ¨λ¦¬ λˆ„μˆ˜κ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.
var continuation: Continuation<Unit>? = null

suspend fun suspendAndSetContinuation() {
    suspendCoroutine<Unit> { cont ->
        continuation = cont
    }
}

fun main() = runBlocking {
    println("Before")
    suspendAndSetContinuation()
    continuation?.resume(Unit)
    println("After")
}
  • μœ„μ™€ 같이 runBlocking을 ν™œμš©ν•˜μ—¬ main ν•¨μˆ˜κ°€ μ½”λ£¨ν‹΄μ˜ μ™„λ£Œλ₯Ό κΈ°λ‹€λ¦¬κ²Œ ν•œλ‹€λ©΄ 문제λ₯Ό ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

https://m.yes24.com/Goods/Detail/123034354

 

μ½”ν‹€λ¦° 코루틴 - 예슀24

μ½”ν‹€λ¦° μ „λ¬Έ 강사가 μ•Œλ € μ£ΌλŠ” μ½”ν‹€λ¦° 코루틴에 λŒ€ν•œ λͺ¨λ“  것!μ½”ν‹€λ¦° 코루틴은 효율적이고 μ‹ λ’°ν•  수 μžˆλŠ” λ©€ν‹°μŠ€λ ˆλ“œ ν”„λ‘œκ·Έλž¨μ„ μ‰½κ²Œ κ΅¬ν˜„ν•  수 있게 ν•΄ μ£Όμ–΄ μžλ°” 가상 λ¨Έμ‹ (JVM), 특히 μ•ˆλ“œλ‘œ

m.yes24.com