[Coroutine] ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜

 ๐Ÿ’ก ์ฝ”๋ฃจํ‹ด์˜ ์ƒ๋ช… ์ฃผ๊ธฐ๋ฅผ ์ œ์–ดํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ฃผ๋Š” ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜์— ๋Œ€ํ•˜์—ฌ ํ•™์Šตํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„

์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„์˜ ํ•„์š”์„ฑ

  • ์•„๋ž˜ ์ž‘์—…์€ ์ค‘๋‹จ ํ•จ์ˆ˜์—์„œ ์ค‘๋‹จ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉฐ, ์ž‘์—…์ด ๋™์‹œ์— ์ง„ํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ํ•˜๋‚˜์˜ ์•ค๋“œํฌ์ธํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์–ป๋Š” ๋ฐ 1์ดˆ์”ฉ ๊ฑธ๋ฆฌ๋ฏ€๋กœ ์ด 2์ดˆ ์†Œ๋ชจ
suspend fun getUserProfile() : UserProfileData {
    val user = getUserData() // 1์ดˆ ํ›„ 
    val notifications = getNotifications() // 1์ดˆ ํ›„

    return UserProfileData(
        user = user,
        notifications = notifications,
    )
}
  • ๋‘ ์ค‘๋‹จ ํ•จ์ˆ˜๋ฅผ ๋™์‹œ์— ์‹คํ–‰ํ•˜๋ ค๋ฉด ๊ฐ ํ•จ์ˆ˜๋ฅผ async๋กœ ๋ž˜ํ•‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ async๋Š” ์Šค์ฝ”ํ”„๋ฅผ ํ•„์š”๋กœ ํ•˜๋ฉฐ GlobalScope๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฑด ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์•„๋‹ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์ด๋Š” EmptyCorotineContext๋ฅผ ๊ฐ€์ง„ ์Šค์ฝ”ํ”„์ผ ๋ฟ์ด๋ฉฐ, async๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ถ€๋ชจ ์ฝ”๋ฃจํ‹ด๊ณผ ์•„๋ฌด๋Ÿฐ ๊ด€๊ณ„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

async์˜ ๋‹จ์ 

  • ์ทจ์†Œ๊ฐ€ ์–ด๋ ค์›€
    • ๋ถ€๋ชจ๊ฐ€ ์ทจ์†Œ๋˜์–ด๋„ async ๋‚ด๋ถ€์˜ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰ ์ค‘์ธ ์ƒํƒœ๊ฐ€ ๋˜๋ฏ€๋กœ ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ์ž์›์ด ๋‚ญ๋น„๋ฉ๋‹ˆ๋‹ค.
  • ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ ์Šค์ฝ”ํ”„๋ฅผ ์ƒ์†๋ฐ›์ง€ ์•Š์Œ
    • ํ•ญ์ƒ ๊ธฐ๋ณธ ๋””์ŠคํŒจ์ฒ˜์—์„œ ์‹คํ–‰๋˜๋ฉฐ, ๋ถ€๋ชจ์˜ ์ปจํ…์ŠคํŠธ๋ฅผ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ด๋Š” ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ CPU๋ฅผ ๋‚ญ๋น„ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฝ”๋ฃจํ‹ด์„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋„๊ตฌ๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š์•„ ํ•จ์ˆ˜๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
  • cancel ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ์Šค์ฝ”ํ”„๋ฅผ ์ทจ์†Œํ•˜๋Š” ๋“ฑ์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ์กฐ์ž‘์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๋‹ค๋ฃจ๊ธฐ ์–ด๋ ค์šฐ๋ฉด์„œ ์ž ์žฌ์ ์œผ๋กœ ์œ„ํ—˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

coroutineScope

  • coroutineScope๋Š” ์Šค์ฝ”ํ”„๋ฅผ ์‹œ์ž‘ํ•˜๋Š” ์ค‘๋‹จ ํ•จ์ˆ˜์ด๋ฉฐ, ์ธ์ž๋กœ ๋“ค์–ด์˜จ ํ•จ์ˆ˜๊ฐ€ ์ƒ์„ฑํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
suspend fun <R> coroutineScope(
    block: suspend CoroutineScope.() -> R
): R
  • async๋‚˜ launch์™€ ๋‹ค๋ฅด๊ฒŒ coroutineScope์˜ ๋ณธ์ฒด๋Š” ๋ฆฌ์‹œ๋ฒ„ ์—†์ด ๊ณง๋ฐ”๋กœ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
  • coroutineScope๋Š” ์ƒˆ๋กœ์šด ์ฝ”๋ฃจํ‹ด์„ ์ƒ์„ฑํ•˜์ง€๋งŒ ์ƒˆ๋กœ์šด ์ฝ”๋ฃจํ‹ด์ด ๋๋‚  ๋•Œ๊นŒ์ง€ coroutineScope๋ฅผ ํ˜ธ์ถœํ•œ ์ฝ”๋ฃจํ‹ด์„ ์ค‘๋‹จํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ํ˜ธ์ถœํ•œ ์ฝ”๋ฃจํ‹ด์ด ์ž‘์—…์„ ๋™์‹œ์— ์‹œ์ž‘ํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.
fun main() = runBlocking {
    coroutineScope {
        delay(1000)
        print(10)
    }
    coroutineScope {
        delay(1000)
        prlint(20)
    }
}

// 1์ดˆ ํ›„ 
// 10
// 1์ดˆ ํ›„
// 20

coroutineScope์˜ ํŠน์ง•

  • ์œ„ ์ฝ”๋“œ์—์„œ ์ƒ์„ฑ๋œ ์Šค์ฝ”ํ”„๋Š” ๋ฐ”๊นฅ์˜ ์Šค์ฝ”ํ”„์—์„œ coroutineContext๋ฅผ ์ƒ์†๋ฐ›์ง€๋งŒ ์ปจํ…์ŠคํŠธ์˜ Job์„ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜์—ฌ ๋ถ€๋ชจ์˜ ์ฑ…์ž„์„ ์ด์–ด๋ฐ›์Šต๋‹ˆ๋‹ค.
    • ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ ์ปจํ…์ŠคํŠธ ์ƒ์†
    • ์ž์‹ ์˜ ์ž‘์—…์ด ๋๋‚ด๊ธฐ ์ „๊นŒ์ง€ ๋ชจ๋“  ์ž์‹์„ ๊ธฐ๋‹ค๋ฆผ
    • ๋ถ€๋ชจ๊ฐ€ ์ทจ์†Œ๋˜๋ฉด ์ž์‹๋“ค ๋ชจ๋‘ ์ทจ์†Œ
  • ์•„๋ž˜์™€ ๊ฐ™์€ coroutineScope ํ•จ์ˆ˜๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.
suspend fun longTask() = coroutineScope {
    launch {
        delay(1000)
        val name = coroutineContext[CoroutineName]?.name
        println("[$name] Finished task 1")
    }
    launch {
        delay(2000)
        val name = coroutineContext[CoroutineName]?.name
        println("[$name] Finished task 2")
    }}
  • ๋ชจ๋“  ์ž์‹์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ํŠน์„ฑ
fun main() = runBlocking(CoroutineName("Parent")) {
    println("Before")
    longTask()
    println("After")
}
// Before
// 1์ดˆ ํ›„
// [Parent] Finished task 1
// 1์ดˆ ํ›„
// [Parent] Finished task 2
// After
  • ๋ถ€๋ชจ๊ฐ€ ์ทจ์†Œ๋˜๋ฉด ์ž์‹๋“ค ๋ชจ๋‘ ์ทจ์†Œ๋˜๋Š” ํŠน์„ฑ
fun main(): Unit = runBlocking {
    val job = launch(CoroutineName("Parent")) {
        longTask()
    }
    delay(1500)
    job.cancel()
}
// [Parent] Finished task 1

coroutineScope์˜ ์˜ˆ์™ธ์ฒ˜๋ฆฌ

  • ์ฝ”๋ฃจํ‹ด ๋นŒ๋”์™€ ๋‹ฌ๋ฆฌ coroutineScope๋‚˜ ์Šค์ฝ”ํ”„์— ์†ํ•œ ์ž์‹์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋‹ค๋ฅธ ๋ชจ๋“  ์ž์‹์ด ์ทจ์†Œ๋˜๊ณ  ์˜ˆ์™ธ๊ฐ€ ๋‹ค์‹œ ๋˜์ ธ์ง‘๋‹ˆ๋‹ค.
    • ์ด๋ฅผ ๊ตฌ์กฐ์  ๋™์‹œ์„ฑ์„ ๋ณด์žฅํ•œ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.
    • ์ฝ”๋ฃจํ‹ด ๋นŒ๋”์ธ launch๋Š” ์˜ˆ์™ธ๊ฐ€ ๋ถ€๋ชจ๋กœ ์ „ํŒŒ๋ฉ๋‹ˆ๋‹ค.
    • ์ฝ”๋ฃจํ‹ด ๋นŒ๋”์ธ async๋Š” ์˜ˆ์™ธ๊ฐ€ Deferred ๊ฐ์ฒด์— ์ €์žฅ๋˜๋ฉฐ, await() ํ˜ธ์ถœ ์‹œ ์˜ˆ์™ธ๊ฐ€ ๋˜์ ธ์ง‘๋‹ˆ๋‹ค.
    • ์ฝ”๋ฃจํ‹ด ๋นŒ๋”๋Š” ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด์€ ๊ณ„์† ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
class ApiEcception(
    val code: Int,
    message: String,
): Throwable(message)

fun getFollowersNumber(): Int
suspend fun getUserName(): String

suspend fun getTweets(): List<Tweet>

suspend fun getUserDetails(): Details = coroutineScope{
    val userName = async { getUserName() }
    val followersNumber = async { getFollowersNumber()}
    Detail(userName.await() , followersNumber.await())
}

fun main() = runBlocking<Unit> {
    val details = try {
        getUserDetails()
    } catch (e: ApiException) {
        null
    }
    val tweets = async { getTeets() }
    println("User: $details")
    println("Tweets: ${tweets.await()}")
}
// User: null
// Tweets: [Tweet(text=Hellow, world)]
  • ์ค‘๋‹จ ํ•จ์ˆ˜์—์„œ ๋ณ‘๋ ฌ๋กœ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๊ฒฝ์šฐ coroutineScope๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

coroutineScope ํ™œ์šฉ

  • coroutineScope๋Š” ์ค‘๋‹จ ๋ฉ”์ธ ํ•จ์ˆ˜ ๋ณธ์ฒด๋ฅผ ๋ž˜ํ•‘ํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • runBlocking ํ•จ์ˆ˜๋ฅผ coroutineScope๊ฐ€ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
suspend fun main(): Unit = coroutineScope {
    launch {
        delay(1000)
        println("World")
    }
    println("Hello, ")
}
  • coroutineScope ํ•จ์ˆ˜๋Š” ๊ธฐ์กด์˜ ์ค‘๋‹จ ์ปจํ…์ŠคํŠธ์—์„œ ๋ฒ—์–ด๋‚œ ์ƒˆ๋กœ์šด ์Šค์ฝ”ํ”„๋ฅผ ๋งŒ๋“ค๋ฉฐ, ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ ์Šค์ฝ”ํ”„๋ฅผ ์ƒ์†๋ฐ›๊ณ  ๊ตฌ์กฐํ™”๋œ ๋™์‹œ์„ฑ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜

  • ์Šค์ฝ”ํ”„๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋‹ค์–‘ํ•œ ํ•จ์ˆ˜๊ฐ€ ์žˆ์œผ๋ฉฐ, coroutineScope์™€ ๋น„์Šทํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜๋Š” ์ฝ”๋ฃจํ‹ด ๋นŒ๋”์™€ ํ˜ผ๋™๋˜์ง€๋งŒ, ๋‘ ํ•จ์ˆ˜๋Š” ๊ฐœ๋…์ ์œผ๋กœ๋‚˜ ์‚ฌ์šฉํ•จ์— ์žˆ์–ด์„œ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์‰ฝ๊ฒŒ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฝ”๋ฃจํ‹ด ๋นŒ๋”(runBlocking์„ ์ œ์™ธํ•œ)์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜

launch, async, produce coroutineScope, supervisorScope, withContext, withTimeout
CoroutineScope์˜ ํ™•์žฅ ํ•จ์ˆ˜ ์ค‘๋‹จ ํ•จ์ˆ˜
CoroutineScope ๋ฆฌ์‹œ๋ฒ„์˜ ์ฝ”๋ฃจํ‹ด ์ปจํ…์ŠคํŠธ๋ฅผ ์‚ฌ์šฉ ์ค‘๋‹จ ํ•จ์ˆ˜์˜ ์ปจํ‹ฐ๋‰ด์—์ด์…˜ ๊ฐ์ฒด๊ฐ€ ๊ฐ€์ง„ ์ฝ”๋ฃจํ‹ด ์ปจํ…์ŠคํŠธ๋ฅผ ์‚ฌ์šฉ
์˜ˆ์™ธ๋Š” Job์„ ํ†ตํ•ด ๋ถ€๋ชจ๋กœ ์ „ํŒŒ ์ผ๋ฐ˜ ํ•จ์ˆ˜์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
๋น„๋™๊ธฐ์ธ ์ฝ”๋ฃจํ‹ด์„ ์‹œ์ž‘ ์ฝ”๋ฃจํ‹ด ๋นŒ๋”๊ฐ€ ํ˜ธ์ถœ๋œ ๊ณณ์—์„œ ์ฝ”๋ฃจํ‹ด ์‹œ์ž‘
  • runBlocking์„ ์ œ์™ธํ•œ ์ด์œ ๋Š” runBlocking์€ ์ฝ”๋ฃจํ‹ด ๋นŒ๋”๋ณด๋‹ค ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜์™€ ๋น„์Šทํ•œ ์ ์ด ๋”๋งŽ์•„๋ณด์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • runBlocking ๋˜ํ•œ ํ•จ์ˆ˜ ๋ณธ์ฒด๋ฅผ ๊ณง๋ฐ”๋กœ ํ˜ธ์ถœํ•˜๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, runBlocking์€ ๋ธ”๋กœํ‚น ํ•จ์ˆ˜์ง€๋งŒ ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜๋Š” ์ค‘๋‹จ ํ•จ์ˆ˜๋ผ๋Š” ์ ์ž…๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ runBlocking์€ ์ฝ”๋ฃจํ‹ด ๊ณ„์ธต์—์„œ ๊ฐ€์žฅ ์ƒ์œ„์— ์žˆ์œผ๋ฉฐ, ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜๋Š” ์ค‘๊ฐ„ ๊ณ„์ธต์— ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

withContext

  • withContext๋Š” coroutineScope์™€ ๋น„์Šทํ•˜์ง€๋งŒ ์Šค์ฝ”ํ”„์˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ํŠน์ง•์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
  • withContext์˜ ์ธ์ž๋กœ ์ปจํ…์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•˜๋ฉด ์ฝ”๋ฃจํ‹ด ๋นŒ๋”์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋ถ€๋ชจ ์Šค์ฝ”ํ”„์˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
withContext(CoroutineName("Child 1")) {
    delay(1000)
    log("Child 1")
}
  • ์ฃผ๋กœ ๊ธฐ์กด ์Šค์ฝ”ํ”„์™€ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
launch(Dispatchers.Main) {
    view.showProgressBar()
    withContext(Dispathcers.IO){
        filePepository.saveData(data)
    }
    view.hidePrgoressBar()
}

supervisorScope

  • supervisorScope๋Š” ํ˜ธ์ถœํ•œ ์Šค์ฝ”ํ”„๋กœ๋ถ€ํ„ฐ ์ƒ์†๋ฐ›์€ coroutineScope๋ฅผ ๋งŒ๋“ค๊ณ  ์ง€์ •๋œ ์ค‘๋‹จ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋Š” ์ ์—์„œ coroutineScope์™€ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค.
  • ๋‘˜์˜ ์ฐจ์ด๋Š” ์ปจํ…์ŠคํŠธ์˜ Job์„ SupervisorJob์œผ๋กœ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ž์‹ ์ฝ”๋ฃจํ‹ด์ด ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋”๋ผ๋„ ์ทจ์†Œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
fun main() = runBlocking {
    println("Before")

    supervisorScope {
        launch {
            delay(100)
            throw Error()
        }

        launch {
            delay(2000)
            println("Done")
        }
    }

    println("After")
}
// Before
// 1์ดˆ ํ›„
// ์˜ˆ์™ธ ๋ฐœ์ƒ
// 1์ดˆ ํ›„
// Done
// After
  • supervisorScope๋Š” ์„œ๋กœ ๋…๋ฆฝ์ ์ธ ์ž‘์—…์„ ์‹œ์ž‘ํ•˜๋Š” ํ•จ์ˆ˜์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
suspend fun notifyAnalytics(actions: List<UserAction>) =
    supervisorScope {
        actions.forEach { action ->
            launch {
                notifyAnalytics(action)
            }
        }
    }
  • async์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ
    • async๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ถ€๋ชจ๋กœ ์ „ํŒŒ๋˜๋Š” ๊ฑธ ๋ง‰๊ธฐ ์œ„ํ•œ ์ถ”๊ฐ€์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
    • await๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  async ์ฝ”๋ฃจํ‹ด์ด ์˜ˆ์™ธ๋กœ ๋๋‚˜๊ฒŒ ๋œ๋‹ค๋ฉด await๋Š” ์˜ˆ์™ธ๋ฅผ ๋‹ค์‹œ ๋˜์ง€๊ธฐ ๋ฉ๋‹ˆ๋‹ค.
    • ๋”ฐ๋ผ์„œ async์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ์ „๋ถ€ ์ฒ˜๋ฆฌํ•˜๋ ค๋ฉด try-catch ๋ธ”๋ก์œผ๋กœ await ํ˜ธ์ถœ์„ ๋ž˜ํ•‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
// async๋ฅผ ํ™œ์šฉํ•œ ๊ฒฝ์šฐ
superviorScope {
    articleRepositories
        .map { async { it.fetchArticles() } }
        .mapNotNull {
            try {
                it.await()
            } catch (e: Throwable) {
                e.printStackTrace()
                null
            }
        }
}
  • withContext(SupervisorJob())์„ ์“ฐ๋ฉด ์•ˆ๋˜๋Š” ์ด์œ 
    • supervisorScope ๋Œ€์‹  withContext(SupervisorJob())์„ ํ™œ์šฉํ•  ๊ฒฝ์šฐ ์ž˜๋ชป ๋œ ์ฝ”๋“œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • withContext๋Š” ์—ฌ์ „ํžˆ ๊ธฐ์กด์— ๊ฐ€์ง€๊ณ  ์žˆ๋˜ Job์„ ์‚ฌ์šฉํ•˜๋ฉฐ SupervisorJob()์ด ํ•ด๋‹น ์žก์˜ ๋ถ€๋ชจ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
    • ๋”ฐ๋ผ์„œ ํ•˜๋‚˜์˜ ์ž์‹ ์ฝ”๋ฃจํ‹ด์ด ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค๋ฉด ๋‹ค๋ฅธ ์ž์‹๋“ค ๋˜ํ•œ ์ทจ์†Œ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
    • ๊ฒฐ๊ตญ SupervisorJob()์ด ํ•„์š”๊ฐ€ ์—†์–ด์ง€๋ฉฐ, withContext ๋˜ํ•œ ์˜ˆ์™ธ๋ฅผ ๋˜์ง‘๋‹ˆ๋‹ค.

withTimeout

  • coroutineScope์™€ ๋น„์Šทํ•œ ํ•จ์ˆ˜์ด๋ฉฐ, ์Šค์ฝ”ํ”„๋ฅผ ๋งŒ๋“ค๊ณ  ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ์ธ์ž๋กœ ๋“ค์–ด์˜จ ๋žŒ๋‹ค์‹์„ ์‹คํ–‰ํ•  ๋•Œ ์‹œ๊ฐ„ ์ œํ•œ์ด ์žˆ์œผ๋ฉฐ, ์‹œ๊ฐ„์ด ๋„ˆ๋ฌด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋ฉด ๋žŒ๋‹ค์‹์„ ์ทจ์†Œํ•˜๊ณ  TimeoutCancellationException์„ ๋˜์ง‘๋‹ˆ๋‹ค.
    • ์ด ๋•Œ TimeoutCancellationException์€ CancellationException์˜ ์„œ๋ธŒํƒ€์ž…์ž…๋‹ˆ๋‹ค.
suspend fun test(): Int = withTimeout(1500){
    delay(1000)
    println("Still thinking")
    delay(1000)
    prntln("Done!")
    42
}
// 1 ์ดˆ ํ›„
// Still thinking
// 0.5์ดˆ ํ›„
// TimeoutCancellationException
  • ์ด๋Š” ํ…Œ์ŠคํŠธํ•  ๋•Œ ์œ ์šฉํ•˜๋ฉฐ, ํŠน์ • ํ•จ์ˆ˜๊ฐ€ ์‹œ๊ฐ„์ด ์–ผ๋งˆ๋‚˜ ๊ฑธ๋ฆฌ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์šฉ๋„๋กœ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • runTest ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, withTimeout์€ ๊ฐ€์ƒ ์‹œ๊ฐ„์œผ๋กœ ์ž‘๋™ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜ ์—ฐ๊ฒฐํ•˜๊ธฐ

suspend fun calcuateAnswerOrNull(): User? =
    withContext(Dispatchers.Default) {
        withTimeoutOrNull(1000){
            calcuateAnswer()
        }
    }
  • ์„œ๋กœ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜์˜ ๋‘ ๊ฐ€์ง€ ๊ธฐ๋Šฅ์ด ๋ชจ๋‘ ํ•„์š”ํ•˜๋‹ค๋ฉด ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜์—์„œ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง€๋Š” ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ถ”๊ฐ€์ ์ธ ์—ฐ์‚ฐ

  • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋„์ค‘์— ์ถ”๊ฐ€์ ์ธ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
suspend fun showUserData() = coroutineScope {
    val user = User(
        name = name.await(), // async
        profile = profie.await() // async
    )
    view.show(user)
    launch {
        repo.notifyProfileShown()
    }
}

fun onCreate(){
    viewModelScope.launch {
        _progressBar.value = true
        showUserData()
        _progressBar.value = false
    }
}
  • ์ด ๋ฐฉ์‹์—๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฌธ์ œ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • [ ํ•จ์ˆ˜์˜ ๋ชฉ์ ์„ ๋ฒ—์–ด๋‚œ ์ง€๋‚˜์นœ ๊ธฐ๋‹ค๋ฆผ ]
    • coroutineScope๊ฐ€ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๊ณ  ๋ณด์—ฌ์ค€ ํ›„ notifyProfileShown()๋ฅผ ์‹คํ–‰ํ•˜๋ฏ€๋กœ, ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•  ๋•Œ ๋‚˜ํƒ€๋‚ผ _progressBar๋Š” notifyProfileShown()๊ฐ€ ์ข…๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋ณด์—ฌ์ง‘๋‹ˆ๋‹ค.
    • ์ด๋Ÿฐ ๊ฒฝ์šฐ showUserData() ํ•จ์ˆ˜๋กœ๋ถ€ํ„ฐ ๊ธฐ๋Œ€ํ•˜๋Š” ์œ ์˜๋ฏธํ•œ ์ž‘์—…์„ ํ•œ๋‹ค๊ณ  ๋ณด๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
  • [ ์ทจ์†Œ ]
    • ์ฝ”๋ฃจํ‹ด์€ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋‹ค๋ฅธ ์—ฐ์‚ฐ์„ ์ทจ์†Œํ•˜๊ฒŒ ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.
    • notifyProfileShown()์ด profie.await()์— ์˜ํ•ด ์ทจ์†Œ๋œ๋‹ค๋ฉด ์ „์ฒด ๊ณผ์ •์ด ์ทจ์†Œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์ด ๋•Œ ํ•ต์‹ฌ ๋™์ž‘์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š” ์ถ”๊ฐ€์ ์ธ ์—ฐ์‚ฐ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ( ์œ„์—์„œ๋Š” notifyProfileShown()) ๋‹ค๋ฅธ ์Šค์ฝ”ํ”„์—์„œ ์‹œ์ž‘ํ•˜๋Š” ํŽธ์ด ๋‚ซ์Šต๋‹ˆ๋‹ค.
    • ์ด ๋•Œ ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์€ ์—ฐ์‚ฐ์„ ์œ„ํ•œ ์Šค์ฝ”ํ”„๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
val analyticsScope = CoroutineScope(SupervisorJob())
  • ์ด๋ฅผ ํ™œ์šฉํ•ด์„œ ์œ„ ์ฝ”๋“œ๋ฅผ ์•„๋ž˜์ฒ˜๋Ÿผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
class ShowUserDataUseCase(
    ...
    private val analyticsScope: CorotineScope,
){
    suspend fun showUserData() = coroutineScope {
        val user = User(
            name = name.await(), // async
            profile = profie.await() // async
        )
        view.show(user)
        analyticsScope.launch {
            repo.notifyProfileShown()
        }
    }
}
  • ๋„ค์ด๋ฐ ๋œ ์Šค์ฝ”ํ”„๋ฅผ ์ „๋‹ฌํ•จ์œผ๋กœ์จ ๋…๋ฆฝ์ ์ธ ์ž‘์—…์„ ์‹คํ–‰ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์•Œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ ์ค‘๋‹จ ํ•จ์ˆ˜๋Š” ์ฃผ์ž…๋œ ์Šค์ฝ”ํ”„์—์„œ ์‹œ์ž‘ํ•œ ์—ฐ์‚ฐ์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์Šค์ฝ”ํ”„๊ฐ€ ์ „๋‹ฌ๋˜์ง€ ์•Š์œผ๋ฉด ์ค‘๋‹จ ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ์—ฐ์‚ฐ์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์ข…๋ฃŒ๋˜์ง€ ์•Š์„ ๊ฑฐ๋ผ๋Š” ๊ฑธ ์˜ˆ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.