일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- El
- factory method
- builderPattern
- Design Pattern
- 옵저버 패턴
- 싱글톤
- 디자인패턴
- 프로토타입 패턴
- PrototypePattern
- Kotlin
- r
- 함수형프로그래밍
- 추상팩토리패턴
- Singleton
- designPattern
- ㅓ
- 빌터패턴
- a
- 디자인패턴 #
- F
- Functional Programming
- 추상 팩토리
- Observer Pattern
- 팩토리 메소드
- 코틀린
- Abstract Factory
- ㅋㅁ
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
Cancellation and timeouts 본문
Cancelling coroutine execution (코루틴 실행 취소)
오랜시간 실행되는 애플리케이션에서는 백그라운드 코루틴에 대한 세밀한 제어가 필요하다.
예를 들어 사용자가 코루틴을 시작한 페이지를 닫았을 수 있으며 이제 그 결과가 더 이상 필요하지 않다면, job을 취소할수 있어야 한다.
launch{ }는 실행 중인 코루틴을 취소하는 데 사용할 수 있는 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.")
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.
main이 job.cancel을 호출하자마자 취소되었기 때문에 다른 코루틴의 출력을 볼 수 없습니다.
취소 및 조인 호출을 결합하는 jon extention function인 cancelAndJoin도 있습니다.
Cancellation is cooperative (협력적 취소)
- 코루틴 취소는 cooperative이다.
- 코루틴 코드는 취소 가능하도록 협력적이다.
- kotlinx.coroutines의 모든 suspend Fun은 취소 가능합니다.
- 코루틴 취소를 확인하고 취소되면 CancellationException을 던집니다.
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
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
}
}
}
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.")
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm sleeping 3 ...
job: I'm sleeping 4 ...
main: Now I can quit.
실행하여 5회 반복 후에 job이 자체적으로 완료될 때까지 canellation을 후에도 "I'm sleep"을 계속 인쇄하는지 확인
CancellationException에러를 catch { } 확인하고 다시 외부로 던지지 않으면 동일한 문제가 관찰될 수 있습니다.
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.")
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}@4e21445c
job: I'm sleeping 3 ...
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@4e21445c
job: I'm sleeping 4 ...
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@4e21445c
main: Now I can quit.
try{ ... } catch { ... }는 안티 패턴(일반적이고 흔하게 사용되는)이지만
CancellationException을 다시 발생시키지 않는 runCatching 함수를 사용할 때와 같이 더 다른 방식으로 보여질 수 있다
Making computation code cancellable
computation code를 취소 가능하게 만드는 방법에는 두 가지가 있습니다.
첫 번째는 cancellation 를 확인하는 suspend fun 을 주기적으로 호출하는 것입니다. 그 목적에 좋은 선택인 yield() 함수가 있습니다.
* yield()
- suspend fun
- 현재 코루틴을 실행하고 있는 Thread 및 Thread pool 을 다른 coroutine 에게 양보한다.
두 번째는 cancellation 상태를 명시적으로 확인하는 것입니다.
명시적 확인 --> while(isActive) { ... }
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (isActive) { // cancellable computation loop
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
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.")
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting! main: Now I can quit.
isActive는 CoroutineScope 객체를 통해 코루틴 내부에서 사용할 수 있는 extend attribute
CoroutineScope.isActive -> coroutineContext.isActive -> Job.isActive
Job.isActive. -> job이 시작되고 완료되지 않은 상태이며 sub job이 실행이 완료되지 않으면, active 상태이다
Closing resources with finally
취소 가능한 suspend fun 은 취소 시 CancellationException을 발생시키며 일반적인 방식으로 처리할 수 있습니다.
*Kotlin의 use 함수는 코루틴이 취소될 때 정상적으로 종료 작업을 실행합니다.
val job = launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
} finally {
println("job: I'm running finally")
}
}
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.")
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm running finally
main: I'm tired of waiting! job: I'm running finally main: Now I can quit.
Run non-cancellable block
대부분 모든 close 작업(File close, cancelling a job, or closing any kind of a communication channel)은 일반적으로 non-blocking이고, suspend fun 아니기 때문에 일반적으로 별 문제가 없다. 즉, 일반 함수는 finally{ ... } 안에서 잘 실행됨.
그러나 드물게 취소된 코루틴에서 suspend fun 사용해야한다면,
다음 예제와 같이 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")
}
}
}
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.")
Timeout
코루틴의 실행을 취소하는 가장 명백한 실제 이유는 실행 시간이 제한 시간을 초과했기 때문이다.
job에 대한 참조를 가지고 있다가 일정 시간이 지난뒤 job을 취소하기 위한 별도의 코루틴을 시작할 수 있지만,
이를 수행하는 withTimeout 함수를 이용하면된다.
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
withTimeout에 의해 throw되는 TimeoutCancellationException은 CancellationException의 하위 클래스이다.
cancellation는 예외이기 때문에, 모든 리소스는 일반적인 방식으로 닫힙니다.
try {...} catch(e: TimeoutCancellationException) {...} 블록에서 시간 초과로 코드를 래핑할 수 있습니다.
try{
withTimeout(1300L) {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
} catch (e: TimeoutCancellationException){
println(e)
}
시간 초과에 대해 구체적으로 몇 가지 추가 작업을 수행해야 하거나 withTimeout과 유사하지만 시간 초과 시 예외를 throw하는 대신 null을 반환하는 withTimeoutOrNull 함수를 사용 하는 경우
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")
Asynchronous timeout and resources (비동기식 시간 초과 및 리소스)
withTimeout( ){ ... }의 타임아웃 이벤트는
해당 블록에서 실행 중인 코드와 관련하여 비동기적이며, 타임아웃 블록 내부에서 반환 직전에도 언제든지 발생할 수 있습니다.
주의 해야될점은 블록 내부에서 자원을 열어서 참조하게 되면, 자원을 블록 외부에서 닫고, 해제 할 수 있어야한다.
acquired의 count 를 증가시키고, close()에서 이 카운터를 감소시켜 생성된 횟수를 단순히 추적하는 Resource 클래스를 사용하여 closable resource 만든다.
짧은 시간 초과를 걸고 많은 코루틴을 실행한다.
약간의 지연 후에 withTimeout 블록 내부에서 이 리소스를 획득하고 외부에서 해제합니다.
var acquired = 0
class Resource {
init { acquired++ } // Acquire the resource
fun close() { acquired-- } // Release the resource
}
fun main() {
runBlocking {
repeat(100_000) { // Launch 100K coroutines
launch {
val resource = withTimeout(60) { // Timeout of 60 ms
delay(50) // Delay for 50 ms
Resource() // Acquire a resource and return it from withTimeout block
}
resource.close() // Release the resource
}
}
}
// Outside of runBlocking all coroutines have completed
println(acquired) // Print the number of resources still acquired
}
위의 코드를 실행하면 컴퓨터의 타이밍에 따라 다를 수 있지만 항상 0이 인쇄되지 않는다는 것을 알 수 있습니다.
실제로 0이 아닌 값을 보려면 이 예제에서 시간 초과를 조정해야 할 수도 있다.
이 문제를 해결하려면 withTimeout 블록에서 반환하는 것과 반대로 리소스에 대한 참조를 변수에 저장한다.
runBlocking {
repeat(100_000) { // Launch 100K coroutines
launch {
var resource: Resource? = null // Not acquired yet
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
}
}
}
}
// Outside of runBlocking all coroutines have completed
println(acquired) // Print the number of resources still acquired
'Coroutine > coroutineBasic' 카테고리의 다른 글
Coroutine scope (0) | 2022.07.07 |
---|---|
Coroutine context and dispatchers (0) | 2022.07.06 |
Composing suspending functions (suspend 함수 구성하기) (0) | 2022.07.05 |
coroutine basics(코루틴의 기초 공식문서 번역 설명) (0) | 2022.06.29 |