public interface Job : CoroutineContext.Element {
// ...
}
Job
은 CoroutineContext
의 Element
를 확장한 객체이다.Job
은 백그라운드에서 동작하고 개념적으로 완료 시 종료되는 라이프사이클을 가진 취소 가능한 것이다.
계층 구조의 Job
Job
은 부모-자식 계층 구조로 배열할 수 있다.
이때 부모가 취소되면 모든 자식이 재귀적으로 즉시 취소된다. 그리고 취소 예외(CancellationException
)가 아닌 예외가 있는 자식이 실패하면 그 부모와 결과적으로 다른 모든 자식도 즉시 취소된다.
이러한 예외의 전파는 SupervisoerJob
을 사용하여 예외 전파를 막을 수 있다.
Job의 생성
Job
인터페이스의 가장 기본적인 인스턴스는 다음과 같이 만들어진다.
coroutine job
은 실행 코루틴 빌더(async
, launch
)로 만들어진다. 지정된 코드 블록을 실행하고 이 블록이 완료되면 완료된다.
coroutine job
생성을 위한 코루틴 빌더
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
ContextScope(if (context[Job] != null) context else context + Job())
internal class ContextScope(context: CoroutineContext) : CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun toString(): String = "CoroutineScope(coroutineContext=$coroutineContext)"
}
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
코루틴 빌더를 사용하기 위한 CoroutineScope
는 위와 같이 정의되어 있다.
파라미터로 Job
혹은 Dispatchers
를 넘겨주면 coroutine job
을 생성하기 위해 CoroutineContext
가 준비되는 것이다.
Job
과 Dispatcher
를 활용한 coroutine job
생성 예제
val job = Job() // Job 생성
println("job: $job")
// job을 CoroutineContext로 하는 coroutine job을 코루틴 빌더를 통해 생성
CoroutineScope(CoroutineName("Foo") + job).launch {
println("[${this.coroutineContext[CoroutineName]}]: job: ${this.coroutineContext[Job]}")
println("[${this.coroutineContext[CoroutineName]}]: parent: ${this.coroutineContext[Job]?.parent}")
// launch의 {} 안에 정의되어 있는 CoroutineScope의 코루틴 빌터를 활용한 coroutine job 생성
launch(CoroutineName("Baz")) {
println("[${this.coroutineContext[CoroutineName]}]: job: ${this.coroutineContext[Job]}")
println("[${this.coroutineContext[CoroutineName]}]: parent: ${this.coroutineContext[Job]?.parent}")
}
}
// Dispatchers.Default를 CoroutineContext로 하는 coroutine job을 코루틴 빌더를 통해 생성
CoroutineScope(CoroutineName("Bar") + Dispatchers.Default).launch {
println("[${this.coroutineContext[CoroutineName]}]: job: ${this.coroutineContext[Job]}")
println("[${this.coroutineContext[CoroutineName]}]: parent: ${this.coroutineContext[Job]?.parent}")
}
// 즉시 종료되는 것 방지
Thread.sleep(100)
Job의 완료
CompletableJob
은 Job()
팩토리 함수로 생성된다. CompletableJob.complete
를 호출하면 완료된다.
Job의 결과
개념적으로 Job
의 실행은 결과 값을 생성하지 않는다. Job
은 부수 효과를 위해서만 실행된다. 결과를 생성하는 Job
을 위해서는 Job
을 확장한 Deferred
인터페이스를 사용해야 한다.
public interface Deferred<out T> : Job {
// ...
}
Cancellation cause
코루틴 Job
은 본문의 예외 발생으로 인해 종료되었을 때 예외적으로 완료된다고 한다.CompletableJob
은 CompletableJob.completeExceptionally
를 호출함으로써 예외적으로 완료될 수 있다.
예외적으로 완료된 Job
은 취소된 것으로 간주되며, 해당 예외가 그 Job
의 취소 원인이 된다.
Job
의 정상적인 취소는 실패(failure) 와는 취소를 유발한 예외의 종류에 따라 구분된다.
코루틴이 CancellationException
을 던지면, 이는 정상적으로 취소된 것으로 간주된다.
만약 다른 예외가 취소를 유발했다면, 그 Job
은 실패한 것이다.Job
이 실패하면, 그 부모도 동일한 예외 타입으로 취소되며, 이로써 Job
의 일부를 자식에게 위임할 때도 투명성(transparency)이 보장된다.
Job
의 cancel
함수는 취소 사유로 오직 CancellationException
만 허용한다. 따라서 cancel
을 호출하면 항상 ‘정상적인’ 방식으로 Job
이 취소되며, 이로 인해 부모 Job
이 취소되지는 않는다. 이런 방식 덕분에 부모 Job
은 자신을 취소하지 않고도 자식 Job
들을 (그 자식들의 자식까지 재귀적으로) 취소할 수 있다.
'자바' 카테고리의 다른 글
코틀린 receiver 이해를 위한 예시 정리 (0) | 2025.04.03 |
---|---|
Function literals with receiver 문서 정리 (0) | 2025.04.02 |
SupervisorJob (0) | 2025.03.26 |
CoroutineScope (0) | 2025.03.25 |
스레드 비용 (1) | 2025.03.12 |