https://kotlinlang.org/docs/cancellation-and-timeouts.html
์ ๋งํฌ๋ฅผ ๊ณต๋ถํ๋ฉฐ ๋จ๊ธฐ๋ ๊ธฐ๋ก์ด๋ค.
Coroutine cancellation
์ด์ ์ Job์ ์ทจ์ํ ์ ์๋ค๊ณ ์ ์๋ค.
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
job.join() // waits for job's completion
println("main: Now I can quit.")
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlcase(action) }
for (index in 0 until times) {
action(index)
}
}
repeat๋ถํฐ ๋ณด๋ฉด ์ธ๋ผ์ธ ํจ์๋ก ๋๋ค๊ตฌ๋ฌธ์ times๋งํผ ๋ฐ๋ณตํ๋ค. ์์์๋ 1000๋ฒ ๋ฐ๋ณต๋๋ค. 500ms๋๋ ์ด์ ํจ๊ป ๋ฌธ์์ด์ด ์ถ๋ ฅ๋๊ณ ์ด๊ฒ ๊ณ์ ๋ฐ๋ณต๋๋ค. job๊ณผ ๋์์ ๋ฐ ์ค์ฝํ์์๋ ๋๋ ์ด๊ฐ ์๋๋ฐ ๋ฐ์ 1300ms์ด๋ค. ๊ทธ๋ฌ๋ฉด ๊ฒฐ๊ณผ์ ์ผ๋ก repeat์์ ๋ฌธ์์ด์ด 3๋ฒ ์ถ๋ ฅ๋๋ค. ๊ทธ๋ฆฌ๊ณ 3๋ฒ ์ถ๋ ฅ ํ 500ms๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ์ฌ์ด์ ๋ฐ ์ค์ฝํ์ ๋ฌธ์์ด์ด ์ถ๋ ฅ๋๊ณ job.cancel()์ด ํธ์ถ๋๋ฉด์ ์คํ์ค์ด๋ job ์ฝ๋ฃจํด์ ์ทจ์ ์ํจ๋ค. join์ job์ด ๋๋ ๋ ๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ํจ์์ธ๋ฐ join์ ํด๋ job์ด ์ทจ์๋ผ์ ๋ง์ง๋ง ๋ฌธ์์ด ์ถ๋ ฅ ์ ์ ๋ค๋ฅธ ๋ฌธ์์ด์ ๋ณผ ์ ์๊ฒ๋๋ค.
CancellationException
๋ชจ๋ suspend fun์ ์ทจ์๊ฐ๋ฅํ๋ค. ์ทจ์๋๋ฉด CancellationException ์์ธ๋ฅผ ๋ฐํ ํ์ง๋ง ์ด ์์ธ๋ฅผ ์ฝ๋ฃจํด ๋ด์์ ์ธ์งํ์ง ๋ชปํ๋ฉด ์ทจ์๋์ง์๋๋ค.
while (i < 5) { // computation loop, just wastes CPU
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
job ๋ด๋ถ์ while๋ฌธ์ด๋ค. ์ด ๊ตฌ๋ฌธ์ cancel์ด ํธ์ถ๋๋ ๋ฐ๋ณต๋ฌธ ์ฐ์ฐ ์ค์ด๋ผ์ ์ค๊ฐ์ ์ค๋จ๋์ง์๋๋ค. ๋ฐ๋ณต๋ฌธ์ด๋ผ ๋์ ์ ์๋ค์ด์จ๋ค๋ฉด ๋ค์ ์์๋ก ๋ ์ ์ ์ ์๋ค.
val job = launch(Dispatchers.Default) {
repeat(5) { i ->
try {
// print a message twice a second
println("job: I'm sleeping $i ...")
delay(500)
} catch (e: Exception) {
// log the exception
println(e)
}
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
๋งจ ์ฒ์ ์ฝ๋์ ๊ฐ์ ๋ด์ฉ์ด๋ค. cancel๊ณผ join์ ํ๋ฒ์ ํด์ฃผ๋ cancelAndJoin ํจ์๊ฐ ์๊ฒผ๊ณ job ์ฝ๋ฃจํด ๋ด๋ถ์ try-catch๋ก ์์ธ๋ฅผ ์ก๋๋ค. ์ด๋ฌ๋ฉด cancel์ ํธ์ถํ์๋ ์ฝ๋ฃจํด ๋ด๋ถ์์ ๋ฐ์ํ๋ cancellationException์ try-catch๋ฌธ์ด ์ฒ๋ฆฌ๋ฅผ ํด๋ฒ๋ ค์ ์ทจ์๊ฐ ๋์ง์๊ณ ์๋์ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅํ๋ค.
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@6f3a6402
job: I'm sleeping 3 ...
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@6f3a6402
job: I'm sleeping 4 ...
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@6f3a6402
main: Now I can quit.
์ด๋ฐ ์์น์๋ ์กฐ์์ ํผํ๊ธฐ ์ํด ๋ฐ๋ณต๋ฌธ ์ฐ์ฐ์ ์กฐ๊ฑด์ isActive๋ก ๋ฐ๊ฟ์ฃผ๋ฉด ๋ฐ๋ก ํด๊ฒฐ๋๋ค.
while (isActive) { // cancellable computation loop
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
isActive ํ๋กํผํฐ๋ job์ด completed๊ฑฐ๋ cancelled๊ฐ ์๋ ๋ true๋ฅผ ๋ฐํํ๋ค. job์ cancelํธ์ถ๋์๋ isActive๊ฐ ๊ฐ์งํด์ false๋ฅผ ๋ฐํํ๊ณ , job์ด ์์ ํ๊ฒ ์ทจ์๋๋ค.
try-catch์ผ๋ ๋ฐ์ํ๋ ๋ฌธ์ ๋ try-finally๋ฅผ ์ฌ์ฉํ๋ฉด ๋ ์ฝ๊ฒ ์ฒ๋ฆฌ๊ฐ๋ฅํ๋ค.
withContext
cancel์์ ์ค์ ํด์ผ๋๋ ์์ ์ด ์์ ์๋ ์๋ค. ๋ ์์ ํ ์ข ๋ฃ๋ฅผ ์ํด์ ์์๋ก ์ ์ฅํ๋ค๋๊ฐ ํ๋ ์์ ์ ํ๋ ค๋ฉด withContext์ NonCancellable์ ์ฌ์ฉํด์ผํ๋ค.
finally {
withContext(NonCancellable) {
println("job: I'm running finally")
delay(1000L)
println("job: And I've just delayed for 1 sec because I'm non-cancellable")
}
}
withContext๋ ์ฝ๋ฃจํด ์ค์ฝํ๋ฅผ ๋๋ค๋ก ๋ฐ๋๋ค. ์ค๊ดํธ ์์ ์๋ ์์ ๋ค์ ๊ทธ๋์ ์ฝ๋ฃจํด์ด๋ค. withContext๋ context๋ก ๋ถ๋ชจ์ค์ฝํ๋ฅผ ๋ฐ์ผ๋ฉฐ ์คํ๋ ์ฝ๋ฃจํด์ ๊ฒฐ๊ณผ๋ ๋ถ๋ชจ ์ค์ฝํ์๊ฒ ๋ฐํ๋๋ค. ๋ถ๋ชจ ์ค์ฝํ์ context๋ฅผ ๋ฐ์ ๋ cancellation exception์ ํ์ธํ๋๋ฐ active์ํ๊ฐ ์๋๋ฉด ๋ฐ๋ก withContext๋ cancel ์์ธ๋ฅผ ๋์ง๋ค.
๋ ์ทจ์์์ ์ ์ํํ๋ ค๋ฉด Dispatcher๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ๊ฐ๋ฅํ๊ธฐ๋๋ฌธ์ withContext(NonCancellable)๊ณผ ๊ฐ์ด ๋ฃ์ด๋ฒ๋ฆฌ๋ฉด ์ทจ์ํ ์ ์๋ ์์ ์ ์คํ์ํค๋ ๊ฒ ๋๋ค.
withTimeout withTimeoutOrNull
์์ ๊ฐ์ ์ทจ์์์ ์ด ํ์ํ ์ด์ ๋ ์๊ฐ์ด๊ณผ(runtime exceed)์ ๊ฐ๋ฅ์ฑ ๋๋ฌธ์ด๋ค. ์ทจ์์์ ์์ด timeout์ด ๋ฐ์ํ๋ ๊ฒฝ์ฐ๋ฅผ ์ ์ดํ๋ ค๋ฉด withTimeout์ด๋ withTimeoutOrNull์ ์ด์ฉํด์ผํ๋ค.
๋ ํจ์์ญ์ suspend fun์ด๊ณ timeout์ด๋ผ๋ฉด TimeoutCancellationException์ด๋ Null(OrNullํจ์์ ๊ฒฝ์ฐ)์ ์์ธ๋ก ๋์ง๋ค.
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
val result = withTimeoutOrNull(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
"Done" // will get cancelled before it produces this result
}
println("Result is $result")
์ง์ ํ ์๊ฐ์ ๋์ด๊ฐ๋ฉด ์์ธ๋ฅผ ๋์ง๋ค. ๋๋ฒ์งธ๋ null์ ๋์ง๋๊น result์๋ null์ด ์ ์ฅ๋๋ค.
๋น๋๊ธฐ๋ก timeout์ ์ฌ์ฉํ ๋๋ ๋ฆฌ์์ค์ ์ด ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์๋ค. timeout์ด ๋๋๋ ํด๋น ๋ธ๋ก์์ ์ฌ์ฉํ๋ ๋ฆฌ์์ค๋ ํด์ ๋์ง์๊ธฐ ๋๋ฌธ์ธ๋ฐ withTimeout์ด ์์ธ๋ฅผ ๋์ง๋ค๋ ์ ์ ํ์ฉํ์ฌ ๋ฆฌ์์ค ํด์ ๋ฅผ ์์ ํ๊ฒ ํ ์ ์๋ค.
try {
withTimeout(60) { // Timeout of 60 ms
delay(50) // Delay for 50 ms
resource = Resource() // Store a resource to the variable if acquired
}
// We can do something else with the resource here
} finally {
resource?.close() // Release the resource if it was acquired
}
"๋๊ธ, ๊ณต๊ฐ ๋ฒํผ ํ ๋ฒ์ฉ ๋๋ฅด๊ณ ๊ฐ์ฃผ์๋ฉด ํฐ ํ์ด ๋ฉ๋๋ค"
'Android ๐ฅ๏ธ > Coroutine๐' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Coroutine exceptions handling (0) | 2024.03.29 |
---|---|
Asynchronous Flow - Intermediate flow operators~ (1) | 2024.03.29 |
Coroutine context and dispatchers (0) | 2024.03.29 |
Coroutine Composing suspending functions (0) | 2024.03.29 |
Coroutines basics - runBlocking, launch, job, coroutineScope (0) | 2024.03.29 |