๋ณธ ๊ฒ์๊ธ์ 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์์ ์ฝ๋ ๋ด๋ถ ๋์์ ์ง์คํ๊ธฐ ์์ํด์ง๋ค.
'๊นจ์ ๊ฐ๋ ๐ > Kotlin' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Kotlin] Coroutines - runBlocking (0) | 2024.02.13 |
---|---|
[Kotlin] Coroutines - Coroutine builder (0) | 2024.02.12 |
[Kotlin] Coroutines - Suspend vs Block (0) | 2024.02.09 |
[Kotlin] Coroutines - ๋๊ธฐ ์ฝ๋, ๋น๋๊ธฐ ์ฝ๋ (0) | 2024.02.08 |
[Kotlin] Coroutines - ๊ธฐ์ด (0) | 2024.02.07 |