https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html
์ ๋งํฌ๋ฅผ ๊ณต๋ถํ๋ฉฐ ๋จ๊ธฐ๋ ๊ธฐ๋ก์ด๋ค.
์ฝ๋ฃจํด์์ ์ฌ์ฉํ๋ context๋ ๋ํ์ ์ผ๋ก CoroutineContext ํ์ ์ด ์๋ค. ์ด๋ฒ ํ์ด์ง์์๋ ์ด์ ์ ๋ดค๋ Job ๊ฐ์ฒด์ Dispatcher๋ฅผ ์ดํด๋ณด๊ฒ ๋ค.
Dispatchers and threads
coroutine context์๋ coroutine dispatcher๋ ํฌํจ๋๋๋ฐ ํด๋น ์ฝ๋ฃจํด์ ์คํํ ๋ ์ฌ์ฉํ๋ ์ฐ๋ ๋๋ค. dispatcher๋ก ํน์ ์ฐ๋ ๋๋ฅผ ๋ค๋ฃฐ ์ ์์ผ๋ ์ฝ๋ฃจํด์ ์คํ์ ์ ์ดํ ์ ์๋ค. dispatch์ ๋ป์ด ๋ณด๋ด๋ค๋ผ๋ ์๋ฏธ์ธ๋ฐ ์ฐ๋ ๋ ํ๋ก ๋ณด๋ด๊ฑฐ๋ ์๋๋ฉด ์๋ณด๋ด๊ณ ์คํํ๊ฑฐ๋ ๋ ์ค ํ๋๋ฅผ ์๋ฏธํ๋ค๊ณ ์ดํดํ๋ค.
์ฝ๋ฃจํด ๋น๋๋ค(lauch, async)์ coroutine context๋ฅผ ์ ํ์ ์ผ๋ก ์ธ์์ ๋ฃ์ ์ ์๋ค. ์ปจํ ์คํธ๋ฅผ ๋ช ์์ ์ผ๋ก ์ฌ์ฉํ๋ค๋ ๋ง์ ์ฐ๋ ๋๋ฅผ ๊ตฌ๋ถํด์ ์ฌ์ฉํ๊ฒ ๋ค๋ผ๋ ์๋ฏธ๊ฐ๋ค.
launch { // context of the parent, main runBlocking coroutine
println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher
println("Default : I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
์ด๊ฑธ ์คํํด๋ณด๋ฉด 1,2,3,4๊ฐ 2,3,1,4 ์์๋๋ก ์ถ๋ ฅ๋๋ค. ๋์คํจ์ฒ๋ฅผ ์ง์ ํ์ง์์ผ๋ฉด ๋ถ๋ชจ ์ฝ๋ฃจํด์ ์ปจํ ์คํธ๋ฅผ ๋ฐ๋๋ค. ์ฌ๊ธฐ์๋ ์ ์ผ ๋ฐ๊นฅ์ runBlocking(main ์ฐ๋ ๋์์ ๋์๊ฐ)์ด ์์ผ๋ 1๋ฒ์ ์ฐ๋ ๋๊ฐ ๋ฉ์ธ์ด๋ค.
๋ํดํธ ๋์คํจ์ฒ๋ JVM์ ์๋ ๊ณต์ ์ฐ๋ ๋ ํ์์ ๋์๊ฐ๋ค. ๋์คํจ์ฒ์ ๋ค๋ฅธ ์ปจํ ์คํธ๊ฐ ์ง์ ๋์ง์์ ๊ฒฝ์ฐ ์ฌ์ฉ๋๋ค. ์ด ๋์คํจ์ฒ์์ ์ฌ์ฉํ ๋ณ๋ ฌ์ฒ๋ฆฌ์์ค์ CPU์ฝ์ด ๊ฐ์๋ฅผ ๋ฐ๋ผ๊ฐ์ง๋ง ์ต์ 2๊ฐ๋ก ๋ณ๋ ฌ์ฒ๋ฆฌ๋ฅผ ํ๋ค.
๋ง์ง๋ง ์ฝ๋ฃจํด์์ ๋์คํจ์ฒ๋ ์ฝ๋ฃจํด์ ์ฌ์ฉํ๊ธฐ ์ํ ์ฐ๋ ๋๋ฅผ ํ๋ ์์ฑํ ๊ฒ์ด๋ค. ์ ์ฉ ์ฐ๋ ๋๋ฅผ ํ ๋นํ๋ ๊ฒ์ ๋ฆฌ์์ค๋ฅผ ๋ง์ด ์ฐจ์งํ๊ธฐ ๋๋ฌธ์ ์ค์ ๋ก๋ close๋ก ํด๋น ์ฝ๋ฃจํด ๋์คํจ์ฒ๋ฅผ ๋ซ์์ฃผ๊ฑฐ๋ ์ต์์ ๋ณ์๋ก ์ง์ ํด ์ฑ ์ ์ฒด์์ ์ฌ์ฉํ๊ฒ ํด์ผํ๋ค.
CoroutineDispatcher
๋ชจ๋ ๋์คํจ์ฒ๊ฐ ํ์ฅํด์ ์ฌ์ฉํ๋ ์ถ์ํด๋์ค์ด๋ค. kotlinx.coroutines์ ์๋ ๋์คํจ์ฒ ๋ชฉ๋ก์ ๋ค์๊ณผ ๊ฐ๋ค.
Dispatchers.Default:
๋์คํจ์ฒ์ ๋ฐ๋ก context๊ฐ ์ง์ ๋์ง์์ผ๋ฉด ์ด๊ฒ ์ฌ์ฉ๋๋ค. ๊ณต์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ฐ๋ ๋์ ๊ณต์ฉ ํ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ CPU๋ฆฌ์์ค๊ฐ ๋ง์ด ํ์ํ ์์ ์ ์ ํฉํ๋ค.
Dispatchers.IO:
์์ฒญํ ๋ ์๊ธฐ๋ ์ฐ๋ ๋์ ๊ณต์ ํ์ ์ฌ์ฉํ๊ณ ํ์ผ์ ์ถ๋ ฅ, ์์ผ์ ์ถ๋ ฅ ์ฐจ๋จ๊ณผ ๊ฐ์ ์์ ์ ์ฌ์ฉ๋๋ค. ์ด๊ฑธ Offloading์ด๋ผ๊ณ ํ๋๋ฐ CPU๊ฐ TCP/IP ํ๋ก์ธ์ฑํ๋ ๊ฑธ ๋ง์์ ์ฑ๋ฅ์ ์ฌ๋ฆฌ๋ ์์ ์ด๋ค.
Dispatchers.Unconfined:
์ฝ๋ฃจํด ๋น๋๊ฐ ๋ฐํ๋๋ ์ฒซ ์ง์ ๊น์ง ์ฝ๋ฃจํด์ ์คํํ๋ค. ์ค๋จ๋ ์ฝ๋ฃจํด์ ํด๋น ์ค๋จ ํจ์์์ ์ฌ์ฉํ๋ ์ฐ๋ ๋์์ ์ฌ์์๋๋ค. ๋ฐ์ ์กฐ๊ธ ๋ ์์ธํ ์ ๋ฆฌํ์๋ค.
private ์ฐ๋ ๋ ํ์ newSingleThreadContext, newFixedThreadPoolContext๋ก ์์ฑํ ์ ์๋ค.
Unconfined Dispatcher vs Confined Dispatcher
unconfine์ ์ ํ์ด ์๋์ด๋ผ๋ ์๋ฏธ๋ฅผ ๊ฐ์ง๋ค. unconfined ๋์คํจ์ฒ๋ ์ฝ๋ฃจํด์ ํธ์ถํ ์ฐ๋ ๋์์ ์์ํด ์ฒซ suspend์ง์ ๊น์ง ์คํํ๋ค. ์ง์ฐ์ด ๋๋๋ฉด ์ฝ๋ฃจํด์ ์ฌ๊ฐํ๋ค. ์ด๋ฌํ ์์ ์ ์ค๊ฐ์ ๋ฉ์ถ๊ธฐ ๋๋ฌธ์ cpu time์ ์๋นํ๊ฑฐ๋ ui update๋ฅผ ์ ๋ฐ์ดํธํ๋ ์์ ์๋ ์ ์ ํ์ง์๋ค. unconfined ๋์คํจ์ฒ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ธ๋ถ ์ฝ๋ฃจํด์ค์ฝํ๋ฅผ ์์๋ฐ์ ์ฌ์ฉํ๋ค. ๊ทธ์ค runBlocking์ ํนํ ํธ์ถ์ฉ ์ค๋ ๋๋ก ์ ํ๋๋๋ฐ ์ด๊ฑธ ์์๋ฐ์ unconfined ๋์คํจ์ฒ๋ FIFO ์ค์ผ์ค๋ง์ฒ๋ผ ์ฌ์ฉ๋๋ค๊ณ ํ๋ค.
launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
delay(900)
println("Unconfined : After delay in thread ${Thread.currentThread().name}")
}
launch { // context of the parent, main runBlocking coroutine
println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
delay(1000)
println("main runBlocking: After delay in thread ${Thread.currentThread().name}")
}
ํ๋ฆฐํธ๋ฌธ์ 1,2,3,4๋ผ๊ณ ํ ๋ 1,3,2,4๋ผ๊ณ ๋๋ตํ๋๊ฒ ์ผ๋ฐ์ ์ด๋ค. ์ด๊ฑด unconfined๊ฐ ์์ด๋ ๋น์ฐํ ๊ฒฐ๊ณผ์ธ๋ฐ ๋ค๋ฅธ ์ ์ unconfined ๋์คํจ์ฒ์ ๋๋ฒ์งธ ํ๋ฆฐํธ๋ฌธ์ด๋ค.
Unconfined : After delay in thread kotlinx.coroutines.DefaultExecutor
์ด๋ฐ ๋ฉ์์ง๊ฐ ๋์จ๋ค. ์ฒซ๋ฒ์งธ ํ๋ฆฐํธ๋ฌธ์ ์ถ๋ ฅํ ๋ ์ฌ์ฉํ ์ฐ๋ ๋๋ฅผ ์ฌ์ฉํ์ง์๊ณ ์๋ ๋ชจ์ต์ ๋ณด์ฌ์ฃผ๋๋ฐ ์ด๊ฑด delay๊ฐ ์ฌ์ฉํ๋ ๊ธฐ๋ณธ ์ฐ๋ ๋๋ฅผ ๋ฐ์์ ์ฌ์ฉํ๊ณ ์๋ ๊ฒ์ด๋ค. ๊ทธ๋ผ ์๋ ์ฐ๋ ๋๋ฅผ ์ฌ์ฉํ์ง์๋๋ค๋ ๋ง์ธ๋ฐ unconfined dispatcher๋ฅผ ์ฌ์ฉํ๋ ๋ชฉ์ ์ ์ผ๋ถ์์ ์ฆ์์ํ์ด๋ค. ๋๋ ์ด ํจ์๋ฅผ ๊ฑฐ์น๋ฉด์ suspend๊ฑธ๋ฆฐ์๊ฐ ์ธ๋ถ ์ฝ๋ฃจํด์ ์ฌ๊ฐํ๊ธฐ ๋๋ฌธ์, ๋์ค์ ์คํํ ์์ ์ด ์๋ ์ฝ๋ฃจํด์์๋ ์ฌ์ฉํ๋ฉด ์๋๋ค.
Job in the context
job๋ ์ปจํ ์คํธ์ ์ผ๋ถ์ธ๋ฐ ์๋์ ๊ฐ์ด ํด๋น job์ context๋ฅผ ์ถ์ถํ ์ ์๋ค.
println("My job is ${coroutineContext[Job]}")
์ฌ๊ธฐ์ ์๋กญ๊ฒ ์๊ฒ๋ ์ฌ์ค์ธ๋ฐ isActive ํ๋กํผํฐ๊ฐ coroutineContext[Job]?.isActive == true์ ๋จ์ถ์ด์ด๋ค. ํ์ฌ ์ฝ๋ฃจํด์ ์ถ์ถํด isActive๊ฐ true์ธ์ง ํ์ธํ๋ ๊ณผ์ ์ ์ค์ฌ๋ ๊ฒ์ด๋ค.
Children of a coroutine
์ฝ๋ฃจํด์ด ๋ค๋ฅธ ์ฝ๋ฃจํด ์์์ ์คํ๋๋ฉด ์ธ๋ถ ์ฝ๋ฃจํด์ context๋ฅผ ์์๋ฐ๋๋ค. ๊ทธ ๋ง์ ๋ด๋ถ ์ฝ๋ฃจํด์ Job์ด ์ธ๋ถ ์ฝ๋ฃจํด Job์ ์์์ด ๋๋ค๋ ๋ง์ด๋ค. structured concurrency์ ๊ทผ๊ฑฐํด ๋ถ๋ชจ ์ฝ๋ฃจํด์ด ์ทจ์๋๋ฉด ๊ทธ๋๋ก ์์๊น์ง ์ ํ๋์ด ๋ค ์ทจ์๋๋ค. ํ์ง๋ง ์ด ์ ํํ์์ override ์ํฉ์์๋ ์ ์ฉ๋์ง์๋๋ค.
๋ด๋ถ ์ฝ๋ฃจํด์์ ๋ช ์์ ์ผ๋ก ๋ค๋ฅธ ์ค์ฝํ๋ฅผ ์ง์ ํ ๊ฒฝ์ฐ ๋ถ๋ชจ ์ค์ฝํ๋ฅผ ์์๋ฐ์ง ์๋๋ค. ๋ค๋ฅธ ์๋ก๋ ์๋ ์ฝ๋๊ฐ ์๋ค.
val request = launch {
launch(Job()) {
println("job1: I run in my own Job and execute independently!") //1
delay(1000)
println("job1: I am not affected by cancellation of the request") //2
}
launch {
delay(100)
println("job2: I am a child of the request coroutine") //3
delay(1000)
println("job2: I will not execute this line if my parent request is cancelled") //4
}
}
delay(500)
request.cancel()
println("main: Who has survived request cancellation?") //5
delay(1000)
lauch ์์ ์๋ Job์ ๋ค๋ฅธ ๊ณณ์์ ์จ Job์ด๋ค. ์ด๊ฑธ๋ฐ๋ ์๊ฐ ์ธ๋ถ ์ฝ๋ฃจํด ์ค์ฝํ๋ฅผ ์์ ๋ฐ์ง์๋๋ค. 1,2,3,4,5์ ์ถ๋ ฅ ์์๋ 1,3,5,2๋ค.
1,2 ๊ฐ ๋ค์ด์๋ ์ฝ๋ฃจํด์ ์ธ๋ถ์ Job์ ์์๋ฐ์๊ธฐ ๋๋ฌธ์ request์ ๋ค์ด๊ฐ๋ ์ฝ๋ฃจํด ์ค์ฝํ์๋ ๋ ๋ฆฝ์ ์ด๋ค. ๋ฐ๋ผ์ cancel์ ์ํฅ์ด ์๋ค.
Parental Resposibilities
๋ถ๋ชจ ์ฝ๋ฃจํด์ ์์ ์ฝ๋ฃจํด์ด ๋๋ ๋ ๊น์ง ๊ธฐ๋ค๋ฆฐ๋ค. ์ด๊ฒ ์ญ์ ๊ตฌ์กฐ์ ๋์์ฑ์ ๋ฐ๋ฅธ ๊ท์น์ผ ๊ฒ์ด๋ค.
fun main() = runBlocking<Unit> {
val request = launch {
repeat(3) { i ->
launch {
delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms
println("Coroutine $i is done")
}
}
println("request: I'm done and I don't explicitly join my children that are still active")
}
request.join()
println("Now processing of the request is complete")
}
์ฌ๊ธฐ์ ๋งจ ๋ง์ง๋ง ์ถ๋ ฅ๋ฌธ์ Now~๋ฌธ์ฅ์ด๋ค. join()์ request๊ฐ ๋๋๋ ๊ฒ์ ๊ธฐ๋ค๋ ธ๋ค๊ฐ ์คํ๋๊ธฐ ๋๋ฌธ์ request์ ์์ ์ฝ๋ฃจํด์ด ๋ชจ๋ ์คํ๋๊ณ ๋์ join()์ ์งํํ๋ค.
Combining context elements
launch(Dispatchers.Default + CoroutineName("๋ง๋")) {
println("I'm working in thread ${Thread.currentThread().name}")
}
//์ถ๋ ฅ
I'm working in thread DefaultDispatcher-worker-1 @๋ง๋#2
CoroutineName์ ๋๋ฒ๊น ์ฉ context๋ผ๊ณ ํ ์ ์๋๋ฐ ๋๋ฒ๊ทธ ๋ก๊ทธ์ ํด๋น ์ด๋ฆ์ ๊ฐ๊ณ ๋ก๊ทธ๊ฐ ์ถ๋ ฅ๋๋ค.
Coroutine scope - Class์์ ์ด๋ค๋ฉด
์ฝ๋ฃจํด์ค์ฝํ ์ธ์คํด์ค๋ฅผ ์์ฑํด์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์กํฐ๋นํฐ์ ์ ํํ ์ ์๋ค. CoroutineScope(), MainScope()๋ก ์ธ์คํด์ค๋ฅผ ๋ฐฐ์ ํด ์ฝ๋ฃจํด ์ค์ฝํ๋ฅผ ์กํฐ๋นํฐ ํด๋์ค ๋ด๋ถ์์ ๋ง๋ค ์ ์๋ค. MainScope์ ์ฃผ๋ชฉํด์ผํ๋๋ฐ Dispathcers.Main์ ๊ธฐ๋ณธ ๋์คํจ์ฒ๋ก ์ฌ์ฉํ๊ณ UI๋ฅผ ๊ทธ๋ฆฌ๋ ๋ฐ ์ฌ์ฉ๋๋ ์ค์ฝํ์ด๊ธฐ๋๋ฌธ์ด๋ค.
์ฝ๋ฃจํด ์ค์ฝํ๋ฅผ ๋ง๋ค๊ณ ๋๋ฉด, ์ด์ ์ค์ฝํ์ธ์คํด์ค.launch{ }์ ๊ฐ์ ํํ๋ก ์ฝ๋ฃจํด์ ์คํํ ์ ์๋ค.
fun doSomething() {
// launch ten coroutines for a demo, each working for a different time
repeat(10) { i ->
mainScope.launch {
delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
println("Coroutine $i is done")
}
}
}
์ด๋ ๊ฒ ๋ง๋ ํด๋์ค ๋ฉ์๋๋ ์กํฐ๋นํฐ ์ธ์คํด์ค๋ฅผ ์ด์ฉํด ์คํํ ์ ์๋๋ฐ ์กํฐ๋นํฐ ์ธ์คํด์ค๋ฅผ destroyํ๋ฉด ์ธ์คํด์ค ๋ด๋ถ ์ฝ๋ฃจํด๋ cancel๋๋ค.
"๋๊ธ, ๊ณต๊ฐ ๋ฒํผ ํ ๋ฒ์ฉ ๋๋ฅด๊ณ ๊ฐ์ฃผ์๋ฉด ํฐ ํ์ด ๋ฉ๋๋ค"
'Android ๐ฅ๏ธ > Coroutine๐' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Coroutine exceptions handling (0) | 2024.03.29 |
---|---|
Asynchronous Flow - Intermediate flow operators~ (1) | 2024.03.29 |
Coroutine Composing suspending functions (0) | 2024.03.29 |
Coroutine Cancellation and timeouts (0) | 2024.03.29 |
Coroutines basics - runBlocking, launch, job, coroutineScope (0) | 2024.03.29 |