코루틴 공식 가이드 읽고 분석하기 — Part 2

Myungpyo Shim
5 min readJan 19, 2019

--

공식 가이드 읽기 (2 / 8)

Korean [English]

(목차로 돌아가기)

취소(Cancellation)

코루틴에서 실행되는 모든 중단 함수(suspending function)들은 취소 요청에 응답 가능하도록 구현되어야 합니다. 다시말해 중단 함수는 실행 중 취소 가능한 구간마다 취소 요청이 있었는지 확인하고 요청이 있었다면 실행을 즉시 취소하도록 구현되어야 합니다. kotlinx.coroutines 라이브러리의 모든 중단함수는 이러한 취소 요청에 대응 하도록 구현되어 있습니다.

앞서 이야기 한 것처럼 취소를 지원하는 중단 함수들은 실행하는 동안 취소가 가능한 지점마다 현재 코루틴이 취소 되었는지 확인하며, 만약 취소 되었다면 CancellationException 을 발생시키며 종료합니다.

(ReactiveX Observable 을 구현해 본 경험이 있는 분이라면 Observable의 코드 실행 중 취소 가능한 구간마다 isDisposed() 같은 취소 상태 확인 함수를 이용하여 현재 Observable의 구독 취소 여부를 확인하고 Observable 의 데이터 방출을 정지 하고 종료해야 하는지 판단했던 기억이 있을 것입니다.)

만약 다음과 같이 코루틴을 작성하면 취소 요청이 오더라도 작업을 멈추지 않고 계속 진행합니다. (Sleep 대신 Busy-waiting 구현을 해도 마찬가지.)

이 코루틴을 취소 요청에 친화적인 코드로 만들기 위해서는 취소가 가능한 시점마다 다른 Continuation에 실행 시간을 양보하는 yield() 함수를 호출하거나, CoroutineScope 에 정의 된 isActive 속성을 참조하여 코루틴이 비활성(Inactive) 상태인 경우 작업을 중단하도록 작성하는 방법이 있습니다.

yield() 중단 함수를 사용한 구현은 다음과 같이 작성할 수 있습니다.

isActive 속성을 이용한 구현은 다음과 같습니다.

취소 가능한 중단함수들은 취소되면 CancellationException 을 발생 시키며, 우리는 일반적인 예외처리 방식과 동일하게 이를 처리할 수 있습니다. 만약 예외발생 시 해제해야하는 리소스가 있다면 두가지 방식을 사용할 수 있는데 try ~ finally 구문을 사용하는 방식과 Kotlin use() 함수를 사용하는 방식이 있습니다.

try~finally 방식을 사용하면 다음과 같이 작성할 수 있습니다.

Kotlin use() 함수를 사용하면 다음과 같이 작성할 수 있습니다.

취소 불가능한 코드 블록의 실행 (Run non-cancellable block)

이미 CancellableException 이 발생한 코루틴의 finally 블록 안에서 중단 함수를 호출하면 현재 코루틴은 이미 취소된 상태이기 때문에 CancellationException 이 발생합니다.
보통 리소스를 정리하는 함수들은 넌-블럭킹(Non-Blocking) 으로 동작하기 때문에 이러한 제약이 큰 문제가 되지는 않습니다. 하지만 이미 취소된 코루틴 안에서 동기적으로 어떤 중단 함수를 호출해야 하는 상황이라면 우리는 withContext{ } 코루틴 빌더에 NonCancellable 컨텍스트를 전달하여 이를 처리할 수 있습니다.

타임 아웃 (Timeout)

일반적으로 어떤 코루틴의 실행을 중간에 취소해야하는 경우는 그 수행시간이 너무 길어져 허용할 수 있는 시간을 넘어섰을 경우 입니다. 이 경우 우리는 타임아웃(Timeout) 을 지정하고 이 시간을 넘어설 경우 해당 작업을 취소하도록 구현할 수 있습니다.

일반적으로 취소 요청이 없었음에도 어떤 코루틴의 실행을 중간에 취소해야 하는 경우는 그 코루틴의 수행시간이 허용 가능한 시간보다 길어졌을 경우 입니다. 이러한 경우를 다루기 위해서 우리는 코루틴에 제한 시간(Timeout)을 설정하고 이 시간이 넘어설 경우 코루틴이 취소되도록 구현할 수 있습니다.

이러한 기능을 현재까지 학습해온 코루틴 기본 함수들로 직접 구현해보면 다음과 같이 구현해 볼 수 있습니다.

  1. 제한 시간을 설정 할 대상이 되는 코루틴을 생성합니다.
  2. 일정 시간(Timeout) 지연 후 전달 받은 Job 이 끝나지 않았으면 취소하는 동작을 하는 코루틴을 생성하고, 1번에서 만들고 실행한 코루틴의 Job 객체를 전달합니다.
  3. 테스트를 위해 1번 코루틴은 2번 코루틴에서 설정한 시간보다 긴 수행 시간을 갖도록 구현합니다.

위 구현은 우리가 예상한대로 동작합니다. 다만 위와 같이 대상 코루틴의 Job 객체 참조를 유지하며 별도의 코루틴에서 취소를 처리하도록 하는 일을 매번 하는 것은 번거로운 일입니다. 그래서 코루틴 프레임워크는 이 작업을 대신해줄 withTimeout() 함수가 준비되어 있습니다.

위 함수를 실행하면 다음과 같이 TimeoutCancellationException 이 발생하는데, 이는 예제가 메인 함수에서 바로 실행되었기 때문입니다. 코루틴이 취소될 경우 발생하는 CancellationException 은 코루틴에서 일반적인 종료 상황 중 하나로 간주됩니다.

I’m sleeping 0 …
I’m sleeping 1 …
I’m sleeping 2 …
main : I’m running finally!
Exception in thread “main” kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:122) at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:88)

Process finished with exit code 1

끝.

(목차로 돌아가기)

--

--