Development/Android

[Kotlin Coroutine] Coroutine 취소와 타임아웃

SeungYong.Lee 2025. 3. 31. 12:39
반응형

Coroutine 실행 취소

- launch를 통해 반환되는 job의 cancel로 코루틴의 실행을 취소할 수 있다.

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            println("job: I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // 약간의 시간 동안 delay 한다.
    println("main: I'm tired of waiting!")
    job.cancel() // Job을 cancel한다.
    job.join() // Job의 실행이 완료될 때까지 기다린다. 
    println("main: Now I can quit.")    
}

- main 함수가 job.cancel을 호출하면, Job이 취소되었기 때문에 다른 코루틴의 출력을 확인할 수 없다. Job의 확장 함수로 cancel과 join 호출을 결합한 cancelAndJoin도 있다.

 

- join은 코루틴이 종료될 때까지 기다리는 suspend 함수이다. 결과를 반환하지 않는다.

Coroutines 취소는 협력적이다

- Coroutine의 취소는 협력적이다. Coroutine 코드는 취소 가능하도록 협력해야 한다. 

 

- kotlinx.coroutines 패키지의 모든 일시 중단 함수들은 취소 가능하다.

 

- 그들은 Coroutine이 취소되었는지 확인하고 취소되었을 경우 CancellationException을 발생시킨다.

 

- 하지만 코루틴이 게산 작업 진행 중이고, 중간에 취소를 확인해주지 않으면 계속 진행한다.

fun main() = runBlocking {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 5) { // 계산 루프, CPU를 낭비한다
            // 1초에 두 번 메세지를 출력한다.
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // 약간의 시간 동안 delay 한다
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // Job을 취소하고 실행이 완료될 때까지 기다린다.
    println("main: Now I can quit.")    
}

 

- 또한 취소 시 발생하는 CancellationException을 catch 했다가 다시 throw 하지 않는 경우에도 취소되지 않는다.

fun main() = runBlocking {
    val job = launch(Dispatchers.Default) {
        repeat(5) { i ->
            try {
                // 1초에 두 번 메세지를 출력한다.
                println("job: I'm sleeping $i ...")
                delay(500)
            } catch (e: Exception) {
                // 예외를 로깅한다.
                println(e)
            }
        }
    }
    delay(1300L) // 약간의 시간 동안 delay 한다.
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // Job을 취소하고 완료될 때까지 기다린다.
    println("main: Now I can quit.")    
}

Coroutine의 Computation 코드를 취소 가능하게 만들기

- computation code를 취소 가능하게 만드는 두 가지 접근 방식이 있다.

 

- 첫번째는 주기적으로 일시 중단 함수를 실행시켜서 취소되었는지 확인하도록 하는 것이다. (yield 함수 사용)

 

- 두번째는 명시적으로 취소 상태를 확인하는 것이다.

fun main() = runBlocking {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (isActive) { // 활성 상태를 매번 검사
            // 1초에 두 번 메세지를 출력한다.
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // 약간의 시간 동안 delay 한다
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // Job을 취소하고 실행이 완료될 때까지 기다린다.
    println("main: Now I can quit.")    
}

finally 사용해 리소스 닫기

- 취소 가능한 일시 중단 함수는 취소 시에 CancellationException을 throw하며, 이는 일반적인 방식으로 처리할 수 있다. 예를 들어 try { ... }와finally {... } 구문이나, Kotlin의 use 함수는 Coroutine이 취소될 때 정상적으로 종료 작업을 수행한다.

 

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            println("job: I'm running finally")
        }
    }
    delay(1300L) // 잠시 기다리기
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // Job을 취소하고 완료될 때까지 기다리기
    println("main: Now I can quit.")    
}

실행 취소가 불가능한 블록 실행하기

- 드물게 취소된 Coroutine에 대해 일시 중단을 해야 하는 경우 다음과 같이 withContext함수에 NonCancellable Context를 전달하여 사용하는 withContext(NonCancellable) { ... }를 사용할 수 있다.

val job = launch {
    try {
        repeat(1000) { i ->
            println("job: I'm sleeping $i ...")
            delay(500L)
        }
    } 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")
        }
    }
}

Timeout

- 일정 시간이 지나면 TimeoutCancellationException을 발생시켜 코루틴을 취소시킬 수 있는 방법이 있다.

fun main() = runBlocking {
    withTimeout(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
}

- 결과 값을 null을 반환하고 싶을 때에는 withTimeoutOrNull을 사용할 수도 있다.

fun main() = runBlocking {
    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")
}

 

반응형