brunch

You 슬롯 make anything
by writing

C.S.Lewis

코루틴 취소 (슬롯cellation)

코틀린 코루틴 (9)

코루틴 취소(슬롯cellation)


코루틴 Job에서 슬롯cel()과 join()을 사용하여 코루틴을 취소하는 방법에 대해서 간단히 살펴봤다. 코루틴을 취소하기 위해서는 Job 객체의 슬롯cel() 함수를 이용했다. 그렇다면 다음 코드는 어떤가? 원하는 대로 취소가 되는가? (Dispatchers.Default라는 처음 보는 것은 일단 무시하자.)

실행 결과는 실행할 때마다 조금씩 다르겠지만 아래와 같은 경우가 발생할 수 있다.

Call 슬롯cel()
Job - 1
Job - 2
...(중략)...
Job - 114
Finish Counting
Job - 115
...(중략)...
Job - 299
Job - 300

어떤 문제점이 보이는가? 슬롯cel() 이후에도 코루틴이 취소가 되지 않았다. 그러면 취소되지 않은 현재 상태에서 그럴싸한 결과를 보여주기 위해서 조작할 수 있는 방법은 무엇이 있을까?코루틴 Job에서 다뤄보았듯이 슬롯cel()에 이어join()을 사용하면 결과물이 꽤 괜찮아 보인다.

Call 슬롯cel()
Job - 1
Job - 2
...(중략)...
Job - 299
Job - 300
Finish Counting

슬롯cel()과 join()을 사용하는 경우는 흔하기 때문에 이것을 한 번에 적용할 수 있는 슬롯celAndJoin() 함수도 존재한다.

이렇게 사용할 수 있다.


그러나이경우도결과물이출력하는문구랑어울려서그럴듯해보이는것이지실제로코루틴이취소된상태는아니다.300번의for문이모두동작했기 때문이다. 그러면 어떻게 취소할 수 있을까? 혹시 취소할 수 없는 걸까? 이런 의문보다 먼저 품어야 할 의문이 있다.코루틴 Job에서는 슬롯cel()을 사용해서 분명히 코루틴이 취소되는 것을 확인했다. 그런데 지금은 어째서 취소가 되지 않는 걸까?


코루틴이 정상적으로 취소되었다면 슬롯cellationException발생한다고 했다. 그럼 현재 정상적으로 취소가 되고 있지 않은 것이니 슬롯cellationException이 발생하지 않는다는 말이 된다. 한번 확인해 볼까?

Call 슬롯cel()
Job - 1
...(중략)...
Job - 119
Finish Counting
Job - 120
...(중략)...
Job - 300

슬롯cellationException 발생 시 출력되어야 하는 문구가 보이지 않는다.슬롯cellationException이 발생하지 않았다는 것이 입증되었다.


그러면 다시 의문이 든다.코루틴 Job에서는 슬롯cel()을 사용했을 때는 왜 슬롯cellationException이 발생한 것일까?그때는 launch를 사용할 때Dispatchers.Default와 같은 인자를 지정하지 않았다. 그러면 해당 코루틴은 runBlocking에서 사용하는 메인 스레드에서 실행된다. 메인스레드에서 실행되는 경우 runBlocking 내 코드는 순차적으로 실행된다. 다만 launch는 비동기적으로실행된다. 따라서슬롯cel()이 호출되기 전에 launch 블록이 실행 명령은 받았으나, 실제로 내부에서 한 줄의 코드도 완료되지 않은 시점인 상태에서 슬롯cel()이 호출되었을 수 있다. 그러면 아무런 작업을 하지 않은 상태로취소가 된다. (이 내용은코루틴 빌더에서 언급한 바 있다.)


현재 보고 있는 예제는Dispatchers.Default라는 디스패처를 별도로 지정하고 있다. 디스패처? 이게 무엇인지 몰라도 된다. 일단 이렇게 하면 runBlocking에서 사용하는 메인 스레드가 아닌 다른 스레드에서 코루틴이 실행된다고 알고 넘어가자. 이 경우에는launch 코루틴이 별도의 스레드에서 실행되기 때문에슬롯cel()이 호출되기 전에 launch 블록이 실행될 가능성이 높다.(왜 그런 것인지는 나중에 디스패처를 공부하면 이해할 수 있다.)따라서 취소를 감지하기 위한 별도의 조치를 취해야 한다는 결론에 이른다. 어떤 조치를 할 수 있을까? 앞서 학습한 내용에 기반하여 추론을 할 수 있다. Job을 이야기할 때 상태에 관해서 언급하며 마무리했다. Job의 상태는 다음과 같다.

슬롯

New 상태는 어떤 상태일까? 아래와 같이 CoroutineStart.LAZY 옵션을 사용하면 New 상태가 된다.

val job = launch(start = CoroutineStart.LAZY){
println("Coroutine started")
}

그렇다면 취소를 감지하기 위해서 Job의상태를 이용하면 되지 않을까? 해당 상태에 대한 변경 흐름은다음과 같다.

슬롯

우리가 확인하고자 하는 부분은 슬롯cel 했을 때이다. 흐름을 참고하면 Activie 상태에서 슬롯cel을 하면 슬롯celling 상태가 된다. 이 경우의 상태 변경을 감지하려면 isActive가 true에서 false로 전환되는 것 또는 is슬롯celled가 false에서 true로 전환되는 것을 확인하면 된다. 하지만 is슬롯celled는 내부 API이기 때문에 일반 코드에서 사용하지 않아야 하며 deprecated 되기도 했다. 따라서 isActive를 활용하면 된다.

Call 슬롯cel()
Job - 1
...(중략)...
Job - 76
Job - 77
Job is 슬롯celled
Finish Counting

위 예제 코드처럼 슬롯cel 후 isActivie가 false일 때 슬롯cellationException을 발생시키면 된다. 그러면 코루틴은 취소가 되어 for 문도 멈춘다.

if(!isActive) {
throw 슬롯cellationException()
}

이 부분은 아래와 같은 함수로 대체할 수 있다.

ensureActive()

해당 함수에대해서 추적해 보면 결국 다음과 같은 함수를 만날 수 있다.

public fun Job.ensureActive(): Unit {
if (!isActive) throw get슬롯cellationException()
}

이렇게 취소를 할 때 슬롯cellationException이 발생하면 어떤 장점이 있을까? 다르게 표현하면 그냥 단순히 취소가 되고 아무런 피드백이 없는 경우엔 어떤 문제가 있을까?


만약 취소를 했을 때 코루틴에서 할당한 자원 중 반드시 해제를 해야 하는 작업이 있다면 예외가 발생하는 것이 도움이 된다. catch 문 내에서도 처리할 수 있겠지만 슬롯cellationException이 발생하는 것은 코루틴의 정상적인 취소를 의미하기 때문에 굳이 슬롯cellationException에 대한 catch 문을 작성할 필요가 없다. 대신에 finally 문을 사용하면 된다. finally는 예외가 발생하든 안 하든 항상 실행되니 자원을 해제하는 작업을 하기에 유용하다. 또한 모든 예외 상황에서 자원 정리가 가능하다는 장점도 있다.


만약 취소가 불가능한 코루틴을 만들고 싶으면 어떻게 해야 할까? 상황에 따라 그런 경우가 필요할 수 있다. 그럴 때는 withContext코루틴 빌더에Non슬롯cellable콘텍스트를 전달하면 된다.

실행 결과는 아래와 같다.

Call 슬롯cel()
Finish Counting
Job - 1
...(중략)...
Job - 300

앞선 예제에서 ensureActive()으로 대체하긴 했지만 기존에 취소가 정상적으로 되던 것을 확인한 코드에withContext(Non슬롯cellable)를 추가했을 뿐이다. 이 상태에서는 코루틴이 취소가 되지 않았음을 실행 결과를 통해서 확인할 수 있다.


네트워크 통신은 일반적으로 비동기적으로 실행하지만 무한정 대기하도록 방치하지 않는다. 그래서 지정된 시간을 초과할 경우 SocketTimeoutException을 발생하는 등의 장치가 마련되어 있다.


코루틴에서도 이와 비슷한 장치가 있다. 코루틴의 수행 시간이 예상 밖으로 길어질 경우 설정된 제한 시간을 넘기면 취소할 수 있다. 물론 직접 구현할 수도 있겠지만 withTimeout()라는 함수를 제공한다. withTimeout()은 Timeout슬롯cellationException을 발생시킨다. Timeout슬롯cellationException은 슬롯cellationException의 하위 클래스다.

예제 코드를 실행하면 300ms 후에 코루틴이 취소되는 것을 확인할 수 있다.

Job - 1
Job - 2
Job - 3
Job is 슬롯celled
Exception in thread "main" kotlinx.coroutines.Timeout슬롯cellationException: Timed out waiting for 300 ms


요약하자.

- 단순히 슬롯cel() 호출하는 것으로 모든 코루틴을 취소할 수 있는 것은 아니다.
- 코루틴의 상태를 추적하는 Job을 이용하여 상태에 따른 처리를 통해 코루틴을 취소할 수 있다.
- 취소할 수 없는 코루틴을 만들 수 있다.
- 일정 시간 후에 코루틴을 취소할 수 있다.
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari