๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/Kotlin

[Kotlin] Coroutines - CoroutineScope, CoroutineContext

interfacer_han 2024. 2. 10. 15:31

๋ณธ ๊ฒŒ์‹œ๊ธ€์˜ Coroutine ๊ฐœ๋…์€ Android ๋‚ด์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์„ ์ „์ œ๋กœ ์ž‘์„ฑ๋˜์—ˆ๋‹ค.
 

#1 CoroutineScope

...
import kotlinx.coroutines.*

class MainActivity: AppCompatActivity() {
    ...

    val btnDownloadSampleData = findViewById(R.id.btnDownloadSampleData)
    
    btnDownloadSampleData.setOnClickListener {
        // ์ฝ”๋ฃจํ‹ด์˜ ์˜์—ญ
        CoroutineScope(Dispatchers.IO).launch {
            sampleFunction()
        }
    }
    
    ...
}

์ฝ”๋ฃจํ‹ด์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์–ด๋–ค ์˜์—ญ(Scope) ๋‚ด์—์„œ๋งŒ ์‹คํ–‰๋œ๋‹ค (์ด ๋งํฌ์˜ #3 ์ฐธ์กฐ). ๋”ฐ๋ผ์„œ, ์ฝ”๋ฃจํ‹ด์ด ์•„๋‹Œ ์ฝ”๋“œ์™€ ์‰ฝ๊ฒŒ ๊ตฌ๋ณ„ํ•  ์ˆ˜ ์žˆ๊ณ , ๊ด€๋ฆฌํ•˜๊ธฐ๋„ ์‰ฝ๋‹ค. ๊ทธ ๊ด€๋ฆฌ๋ž€ ์˜ˆ๋ฅผ ๋“ค์–ด, ์ฝ”๋ฃจํ‹ด์˜ ๋™์ž‘์„ ์ธ์ง€ํ•˜๊ฑฐ๋‚˜, ์ฝ”๋ฃจํ‹ด์„ ์ทจ์†Œํ•˜๊ฑฐ๋‚˜, ์ฝ”๋ฃจํ‹ด ๋‚ด์—์„œ ๋ฐœ์ƒ๋˜๋Š” ์˜ˆ์™ธ(Exception) ์ฒ˜๋ฆฌ ๋“ฑ์ด ์žˆ๋‹ค. ์ด๋Ÿฌํ•œ ์˜์—ญ(Scope)์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด CoroutineScope ์ธํ„ฐํŽ˜์ด์Šค๋‹ค.
 
(์—ฌ๋‹ด์œผ๋กœ, CoroutineScope์™€ ๋น„์Šทํ•œ GlobalScope๋ผ๋Š” ๊ฒƒ๋„ ์žˆ๋‹ค. ์ด ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์•ฑ์˜ ๋ชจ๋“  ๋ถ€๋ถ„์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ „์—ญ(Global) ์Šค์ฝ”ํ”„(Scope)๋‹ค. ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ GlobalScope๋ฅผ ์“ธ ์ผ์€ ๊ฑฐ์˜ ์—†๋‹ค๊ณ  ํ•œ๋‹ค.)
 

#2 CoroutineContext

// 1. CoroutineDispatcher ์ง€์ •
val dispatcher = Dispatchers.IO

// 2. CoroutineExceptionHandler
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, exception ->
    println("$exception captured in $coroutineContext")
}

// 3. CoroutineName
val coroutineName = CoroutineName("MyCoroutine")

// 4. Job
val myJob = Job()

// CoroutineContext ์š”์†Œ๋“ค์„ ์กฐํ•ฉ (+์—ฐ์‚ฐ์ž ์˜ค๋ฒ„๋กœ๋”ฉ)
val coroutineContext = dispatcher + exceptionHandler + coroutineName + job

// ์ฝ”๋ฃจํ‹ด ์‹คํ–‰
CoroutineScope(coroutineContext).launch {
    sampleFunction()
}

CoroutineScope ์ธํ„ฐํŽ˜์ด์Šค๋Š” CoroutineContext๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค. CoroutineContext๋Š” ์ฝ”๋ฃจํ‹ด์˜ ์‹คํ–‰ ํ™˜๊ฒฝ์„ ์„ค์ •ํ•œ๋‹ค. ์–ด๋Š ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋  ์ง€, ์ฝ”๋ฃจํ‹ด ์ฝ”๋“œ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒ๋˜๋ฉด ์–ด๋–ป๊ฒŒ ํ•  ์ง€, ์ฝ”๋ฃจํ‹ด์˜ ์ด๋ฆ„์„ ๋ถ€์—ฌํ•œ๋‹ค๋ฉด ๋ญ˜๋กœ ํ•  ์ง€, ์ฝ”๋ฃจํ‹ด ์ฝ”๋“œ์˜ ๋ ˆํผ๋Ÿฐ์Šค ๋ณ€์ˆ˜๋Š” ๋ฌด์—‡์œผ๋กœ ํ•  ์ง€ ๋“ฑ์ด๋‹ค. ์ฝ”๋ฃจํ‹ด ๋‚ด๋ถ€ ์ฝ”๋“œ๋Š” ์ฝ”๋“œ๋Œ€๋กœ Coroutine builder ๋‚ด์—์„œ ์ž˜ ์งœ๋ฉด ๋˜๊ณ , ๊ทธ ๋‚ด๋ถ€ ์ฝ”๋“œ์˜ ์™ธ์ ์ธ ๋ถ€๋ถ„์€ ConroutineContext์—์„œ ๋‹ค๋ฃจ๋ฉด ๋˜๋Š” ๊ฒƒ์ด๋‹ค.
 
CoroutineContext๋Š” ์ตœ๋Œ€ 4๊ฐœ ์š”์†Œ๋ฅผ +์—ฐ์‚ฐ์ž๋กœ ์ด์€ ์กฐํ•ฉ(์—ฐ์‚ฐ์ž ์˜ค๋ฒ„๋กœ๋”ฉ)์œผ๋กœ ์ด๋ฃจ์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ๊ฐ ๊ธฐ๋ณธ๊ฐ’์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ƒ๋žต์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ๋ฌธ๋ฒ• ์ƒ 4 ์š”์†Œ ์ค‘ ์ ์–ด๋„ ํ•˜๋‚˜๋Š” ๋ช…์‹œํ•ด์•ผ ํ•˜์ง€๋งŒ, ์ „๋ถ€ ์ƒ๋žตํ•˜๊ณ  ์‹ถ์œผ๋ฉด CoroutineContext ์ž๋ฆฌ์— EmptyCoroutineContext์„ ๋„ฃ์œผ๋ฉด ๋œ๋‹ค. ๊ทธ 4๊ฐ€์ง€ ์š”์†Œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
 

#3 CoroutineContext - CoroutineDispatcher

CoroutineDispatcher๋Š” ํ•ด๋‹น ์ฝ”๋ฃจํ‹ด์ด ์‹คํ–‰๋  ์Šค๋ ˆ๋“œ๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค. Dispatcher์˜ ์‚ฌ์ „์  ์˜๋ฏธ๋Š” "(์—ด์ฐจ·๋ฒ„์Šค·๋น„ํ–‰๊ธฐ ๋“ฑ์ด ์ •์‹œ ์ถœ๋ฐœํ•˜๋„๋ก ๊ด€๋ฆฌํ•˜๋Š”) ์šดํ–‰ ๊ด€๋ฆฌ์›"์ด๋‹ค. Dispatcher๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ 4๊ฐ€์ง€ ์ข…๋ฅ˜๊ฐ€ ์žˆ๊ณ , ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์—์„œ ์‹ค์งˆ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์€ Dispatchers.Unconfined๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ 3๊ฐ€์ง€๋‹ค. ๋˜,  ์ด 4๊ฐœ์˜ ๊ธฐ๋ณธ Dispatcher ์™ธ์—๋„ Custom Dispatcher๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, Room์ด๋‚˜ Retrofit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ œ์ž‘์‚ฌ๋Š” ์ž์ฒด์ ์ธ Custom Dispatcher๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

 

์˜คํ•ดํ•˜์ง€ ๋ง์•„์•ผ ํ•˜๋Š” ๊ฒƒ์€, ์ฝ”๋ฃจํ‹ด์€ Dispatchers ์ข…๋ฅ˜ ๋ณ„๋กœ ํ•˜๋‚˜์”ฉ ์žˆ๋Š” ์Šค๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์Šค๋ ˆ๋“œ์˜ ์ข…๋ฅ˜ ๋ณ„๋กœ ์žˆ๋Š” ์Šค๋ ˆ๋“œ'ํ’€'์—์„œ ์ƒˆ ์Šค๋ ˆ๋“œ ํ•˜๋‚˜๋ฅผ ํ• ๋‹น๋ฐ›์•„ ์ˆ˜ํ–‰๋œ๋‹ค๋Š” ์ ์ด๋‹ค (์œ ์ผํ•œ ๋ฐ˜๋ก€๋กœ, ์•ˆ๋“œ๋กœ์ด๋“œ์— ์ถ”๊ฐ€๋กœ ์กด์žฌํ•˜๋Š” Dispathcers.Main๋Š” ์Šค๋ ˆ๋“œํ’€์ด ์•„๋‹Œ ๋‹จ์ผ ์Šค๋ ˆ๋“œ๋‹ค). 
 

#3-1 Dispatchers.Main

CoroutineScope(Dispatchers.Main).launch {
    println("Hello, ${Thread.currentThread().name}")
}

// ์ถœ๋ ฅ ๊ฒฐ๊ณผ: Hello, main

UI ์Šค๋ ˆ๋“œ๋ผ๊ณ ๋„ ๋ถˆ๋ฆฐ๋‹ค. ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ UI๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์ผํ•œ ์Šค๋ ˆ๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, Dispatchers.Main์ด ์•„๋‹Œ ์Šค๋ ˆ๋“œ์—์„œ ์˜ˆ๋ฅผ ๋“ค์–ด Toast ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค. ๋˜, Dispatchers.IO ๋ฐ Dispatchers.Default์ฒ˜๋Ÿผ ์Šค๋ ˆ๋“œํ’€์—์„œ ์Šค๋ ˆ๋“œ๋ฅผ ๊บผ๋‚ด์˜ค์ง€ ์•Š๊ณ , ๋‹จ์ผ ์Šค๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์œ ์ผํ•œ ์Šค๋ ˆ๋“œ์ด๊ธฐ๋„ ํ•˜๋‹ค.
 

#3-2 Dispatchers.IO

CoroutineScope(Dispatchers.IO).launch {
    println("Hello, ${Thread.currentThread().name}")
}

// ์ถœ๋ ฅ ๊ฒฐ๊ณผ: Hello, DefaultDispatcher-worker-1

Background ์Šค๋ ˆ๋“œ๋กœ, ์ด Dispatcher๊ฐ€ ์š”์ฒญ๋  ๋•Œ๋งˆ๋‹ค ๋™์ ์œผ๋กœ ์Šค๋ ˆ๋“œํ’€์— ์žˆ๋Š” ์Šค๋ ˆ๋“œ๊ฐ€ ํ• ๋‹น๋œ๋‹ค. ๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ๋„คํŠธ์›Œํฌ ํ†ต์‹ , ํŒŒ์ผ ์ž…์ถœ๋ ฅ์— ์ฃผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค. Dispatchers.IO์™€ Dispatchers.Default๋Š” ๊ฐ™์€ ์Šค๋ ˆ๋“œ ํ’€(Thread pool)์„ ๊ณต์œ ํ•œ๋‹ค. ${Thread.currentThread().name}์ด IODispatcher-worker-1์ด ์•„๋‹Œ ์ด์œ ๋‹ค. ๋ฌผ๋ก  ์Šค๋ ˆ๋“œ ํ’€๋งŒ ๊ฐ™๊ณ , ์ž‘๋™ ๋ฐฉ์‹์€ ์„œ๋กœ ๋‹ค๋ฅด๋‹ค.
 

#3-3 Dispatchers.Default

CoroutineScope(Dispatchers.Default).launch {
    println("Hello, ${Thread.currentThread().name}")
}

// ์ถœ๋ ฅ ๊ฒฐ๊ณผ: Hello, DefaultDispatcher-worker-1

ํฌ๊ธฐ๊ฐ€ ํฐ List๋ฅผ ์ •๋ ฌํ•˜๋Š” ๋“ฑ CPU ์ž์›์„ ๋งŽ์ด ๋จน๋Š” ์ž‘์—…์— ์‚ฌ์šฉ๋˜๋Š” ์Šค๋ ˆ๋“œ๋‹ค. Dispatchers.Default๋Š” CoroutineDispatcher์˜ ๊ธฐ๋ณธ๊ฐ’์ด๋‹ค. ๊ทธ๋ž˜์„œ CoroutineContext์—์„œ CoroutineDispathcer๋ฅผ ์ƒ๋žตํ•˜๋ฉด ์ด ์Šค๋ ˆ๋“œ๊ฐ€ ์‚ฌ์šฉ๋œ๋‹ค.
 

#3-4 Dispatchers.Unconfined

CoroutineScope(Dispatchers.Unconfined).launch {
    println("Hello, ${Thread.currentThread().name}")
}

// ์ถœ๋ ฅ ๊ฒฐ๊ณผ: Hello, main

GlobalScope์™€ ๊ฐ™์ด ์‚ฌ์šฉ๋˜๋Š” Dispatcher๋‹ค. ์ด Dispatcher๋Š” CoroutineScope๊ฐ€ ์‹คํ–‰๋œ ํ˜„์žฌ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ์Šค๋ ˆ๋“œ๊ฐ€ ์ค‘์ง€(Suspended)๋˜์—ˆ๋‹ค๊ฐ€ ์žฌ๊ฐœ(Resumed)๋˜๋ฉด, ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ•ด๋‹น Suspending function์ด ์‹คํ–‰๋˜๋Š” ์Šค๋ ˆ๋“œ์—์„œ ๋‹ค์‹œ ์‹คํ–‰๋œ๋‹ค. ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์—์„œ Dispatchers.Unconfined๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ถŒ์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค. ์œ„์˜ ์ฝ”๋“œ๋Š” MainActivity์—์„œ ์‹คํ–‰ํ•œ ๊ฒƒ์ด๋‹ค. MainActivity๋Š” main ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜๊ธฐ์—, Dispatchers.Unconfined ๋˜ํ•œ main์—์„œ ์‹คํ–‰๋˜์—ˆ๋‹ค.
 

#3-5 Dispatcher ์ƒ๋žต

CoroutineScope(EmptyCoroutineContext).launch {
    println("Hello, ${Thread.currentThread().name}")
}

// ์ถœ๋ ฅ ๊ฒฐ๊ณผ: Hello, DefaultDispatcher-worker-1

Dispatcher์˜ ๊ธฐ๋ณธ๊ฐ’์ธ Dispatchers.Default๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ๋‹ค.
 

#4 CoroutineContext - CoroutineExceptionHandler

#4-1 ์‚ฌ์šฉ

val exceptionHandler = CoroutineExceptionHandler { coroutineContext, exception ->
    println("$exception captured in $coroutineContext")
}

CoroutineScope(exceptionHandler).launch {
    delay(1000) // 1์ดˆ ๋Œ€๊ธฐ
    val result = 1 / 0
    println("Result: $result")
}

/* ์ถœ๋ ฅ ๊ฒฐ๊ณผ
java.lang.ArithmeticException: divide by zero
captured in
[com.example.coroutinesbasics.MainActivity$onCreate$$inlined$CoroutineExceptionHandler$1@f0203a2, StandaloneCoroutine{Cancelling}@2431833, Dispatchers.Default]
*/

CoroutineExceptionHandler๋Š” ์ฝ”๋ฃจํ‹ด ์˜์—ญ(Scope) ๋‚ด์—์„œ ํฌ์ฐฉ๋œ(Captured) ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌ(Handle)ํ•œ๋‹ค.
 

#4-2 ์ƒ๋žต

CoroutineScope(EmptyCoroutineContext).launch {
    delay(1000) // 1์ดˆ ๋Œ€๊ธฐ
    val result = 1 / 0
    println("Result: $result")
}

/* ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ ๋ฐœ์ƒ
FATAL EXCEPTION: DefaultDispatcher-worker-1
Process: com.example.coroutinesbasics, PID: 5066
java.lang.ArithmeticException: divide by zero
    at com.example.coroutinesbasics.MainActivity$onCreate$3.invokeSuspend(MainActivity.kt:49)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
    Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@720b7ee, Dispatchers.Default]
*/

CoroutineExceptionHandler๋ฅผ ์ƒ๋žตํ•˜๋ฉด ๋‹น์—ฐํžˆ ๊ทธ๋ƒฅ ์—๋Ÿฌ๊ฐ€ ๋‚˜ ๋ฒ„๋ฆฐ๋‹ค.
 

#5 CoroutineContext -  CoroutineName

#5-1 ์‚ฌ์šฉ

val coroutineName = CoroutineName("MyCoroutine")

CoroutineScope(coroutineName).launch {
    println("Coroutine name is ${coroutineContext[CoroutineName.Key]}")
}

// ์ถœ๋ ฅ ๊ฒฐ๊ณผ: Coroutine name is CoroutineName(MyCoroutine)

CoroutineName์€ ์ฝ”๋ฃจํ‹ด์— ์ด๋ฆ„์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜์ด๋‹ค. ๋””๋ฒ„๊น…ํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.
 

#5-2 ์ƒ๋žต

CoroutineScope(EmptyCoroutineContext).launch {
    println("Coroutine name is ${coroutineContext[CoroutineName.Key]}")
}

// ์ถœ๋ ฅ ๊ฒฐ๊ณผ: Coroutine name is null

CoroutineName์˜ ๊ธฐ๋ณธ๊ฐ’์€ null์ด๋‹ค.
 

#6 CoroutineContext -  Job

#6-1 ์‚ฌ์šฉ

val countUpJob = Job()

CoroutineScope(countUpJob).launch {
    var count = 0
    while(true) {
        println(count++)
        delay(1000)
    }
}

cancelCountUpButton.setOnClickListener {
    countUpJob.cancel()
}

Job์€ ์ฝ”๋ฃจํ‹ด์˜ ๋ ˆํผ๋Ÿฐ์Šค๋‹ค. Job์„ ๋ณ€์ˆ˜์— ํ• ๋‹นํ•จ์œผ๋กœ์จ ์ต๋ช… ์ฝ”๋ฃจํ‹ด์— ์ด๋ฆ„์„ ๋ถ™์ผ ์ˆ˜ ์žˆ๋‹ค.
 

#6-2 ๊ฐ„ํŽธํ•œ ์‚ฌ์šฉ

val countUpJob = CoroutineScope(Dispatchers.Default).launch {
    var count = 0
    while(true) {
        println(count++)
        delay(1000)
    }
}

cancelCountUpButton.setOnClickListener {
    countUpJob.cancel()
}

์ด๋ ‡๊ฒŒ ํ•ด๋„ #6-1๊ณผ ๋™์ผํ•œ ์ฝ”๋“œ๋‹ค. CoroutineScope.launch์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์ด Job์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. CoroutineScope.async ๋ฐ CoroutineScope.produce๋„ ์œ ์‚ฌํ•œ ๋ฐฉ์‹์œผ๋กœ ๊ฐ„ํŽธํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
 

#6-3 ์ƒ๋žต

CoroutineScope(Dispatchers.IO).launch {
    sampleFunction()
}

// ↑ ์„œ๋กœ ๊ฐ™์€ ์ฝ”๋“œ ↓

CoroutineScope(Dispatchers.IO + Job()).launch {
    sampleFunction()
}

Job()์€ ์‚ฌ์‹ค ์ ˆ๋Œ€๋กœ ์ƒ๋žต๋‹นํ•˜์ง€ ์•Š๋Š”๋‹ค. CoroutineContext์—์„œ Job()์„ ๋„ฃ์ง€์•Š์œผ๋ฉด, ์•Œ์•„์„œ ์ต๋ช… Job()์„ ๋งŒ๋“ค๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. Job()์˜ ๊ธฐ๋ณธ๊ฐ’์€ ์ต๋ช… Job() ์ธ์Šคํ„ด์Šค๋‹ค.
 

#7 ์š”์•ฝ

CoroutineContext๋กœ ์ฝ”๋ฃจํ‹ด์˜ ์‹คํ–‰ ํ™˜๊ฒฝ์„ ํ•œ ๋ฐฉ์— ์ •์˜ํ•˜๊ณ  ๋„˜์–ด๊ฐ„๋‹ค. ๋•๋ถ„์—, Coroutine builder์—์„œ ์ฝ”๋“œ ๋‚ด๋ถ€ ๋™์ž‘์— ์ง‘์ค‘ํ•˜๊ธฐ ์ˆ˜์›”ํ•ด์ง„๋‹ค.