03
29

https://kotlinlang.org/docs/cancellation-and-timeouts.html

 

Cancellation and timeouts | Kotlin

 

kotlinlang.org

์œ„ ๋งํฌ๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉฐ ๋‚จ๊ธฐ๋Š” ๊ธฐ๋ก์ด๋‹ค.

 

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
COMMENT