[Kotlin] Coroutines Job
Job
코루틴에서 백그라운드 작업을 나타내는 인터페이스이다. 인터페이스이긴 하지만 동명의 함수로 Job 객체를 만들 수 있다.
interface Job : CoroutineContext.Element
public fun Job(parent: Job? = null): CompletableJob = ...
Job은 부모-자식 관계로 정렬될 수 있다. 부모 Job이 종료되면 자식 Job도 재귀적으로 모두 종료되며, 자식 Job에서 exception이 발생하면 그 Job의 부모도 즉시 종료되고, 부모의 모든 자식이 재귀적으로 종료된다. 부모의 cancel과 다르게 자식의 failure는 부모 쪽으로 전파되지는 않으며, 자신의 부모에만 영향을 미친다.
Job 객체를 얻는 방법은 크게 두 가지이다. 위에서처럼 ``Job()`` 함수를 실행하여 얻을 수도 있고, ``launch`` 등의 코루틴 빌더가 반환하는 Job을 받을 수도 있다. 보통 첫 번째 방법은 ``CoroutineContext``를 선언할 때 사용하고, 두 번째 방법은 Job을 실제로 컨트롤할 때 사용한다.
Job은 일반적으로 결과값을 반환하지 않는 형태로 설계되었다. ``Deferred``를 통해 값을 비동기적으로 반환할 수 있다.
``Job.complete()``를 호출하면 Job을 종료할 수 있다. Job은 작업이 모두 끝나거나 ``complete()``로 종료될 때 자신의 자식이 모두 종료될 때까지 기다린다. 이때 부모가 자식을 강제로 종료하지는 않으며, 자식의 작업이 모두 끝날 때까지 예의 바르게 기다린다.
``Job.completeExceptionally()``를 호출하면 Job 실행 도중 exception이 발생하여 종료되는 효과를 낼 수 있다. 하지만 이 경우에도 자식 작업이 모두 complete할 때까지 예의 바르게 기다린다.
``CancellationException`` 때문에 종료된 Job은 정상적으로 취소된 것으로 간주된다. 그러나 ``CancellationException``이 아닌 다른 exception이 발생했다면 Job이 실패했다(fail)고 간주된다. Job이 실패하면 그 Job의 모든 부모 역시 같은 이유로 fail한다. 한 부분이라도 실패하면 전체가 실패한 것으로 처리되는 것이다.
그런데 ``Job.cancel()``은 ``CancellationException``만을 매개변수로 받는다. 따라서 ``cancel()``을 호출하면 항상 정상적으로 취소된 것으로 간주되어 부모가 취소되지 않는다. 코드로 시험해 보자.
@OptIn(ExperimentalTime::class)
fun main(): Unit = runBlocking {
val time = measureTimedValue {
launch {
launch {
delay(500)
}
launch {
delay(450)
}
launch {
delay(100)
cancel()
}
}.join()
}
println("Time: ${time.duration}")
}
Time: 525.640600ms
자식 중 하나가 취소되었지만 나머지 자식은 취소되지 않았다.
SupervisorJob
Job에서는 자식이 fail하면 부모도 fail한다고 했다. 하지만 부모가 SupervisorJob이라면 자식 중 하나가 fail해도 부모는 전혀 영향받지 않는다. 부모에서 자식의 fail을 직접 처리하고 싶을 때 사용할 수 있다.
반면 부모 SupervisorJob이 cancel 또는 fail하면 모든 자식이 종료된다. 또, SupervisorJob을 ``CancellationException`` 이외의 다른 exception으로 cancel했을 때 부모 Job 역시 취소된다.
일반적으로 자식의 실패가 부모의 실패를 유발하면 안 되는 상황일 때 ``CoroutineContext``에 정의하여 사용한다.
class SomeClass : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = SupervisorJob()
// ...
}
NonCancellable
항상 active 상태를 유지하는 Job이다. 개념적으로 취소되지 않는 작업을 실행할 때 ``withContext``와 함께 사용하도록 디자인되었다.
withContext(NonCancellable) {
// 블럭 내부는 취소되지 않을 것이다(고 가정해도 좋다)
}
``launch``나 ``async`` 같은 다른 코루틴 빌더 함수에서 사용하지 말자. 항상 active를 유지하는 특성 때문에 부모-자식 간 cancel 관계가 깨질 수 있다.