[Coroutine] Job이란?
먼저 Coroutine의 launch를 눌러서 코드를 살펴보자
- 다음과 같이 우리가 코루틴 빌더를 통해 코루틴을 생성하면 launch는 Job객체를 반환해주는 것을 볼 수 있다.
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
Job이란?
다음으로 Kotlin 공홈에서 말하는 Job에 대해 알아보자.
Job이란 백그라운드 작업을 나타내는 인터페이스이다. 개념적으로 Job은 취소 가능한 것으로 생명주기가 완료될때 종료된다. Job은 부모-자식 계층구조로 구성될 수 있으며, 부모가 취소되면 모든 자식이 재귀적으로 즉시 취소된다. 자식이 CancellationException 이외의 예외로 실패된다면 해당 자식의 부모가 즉시 취소되고, 따라서 그 부모에 엮여있는 모든 자식또한 즉시 취소된다.
Job 인터페이스의 가장 기본적인 인스턴스는 다음과 같이 생성된다.
- launch 코루틴 빌더로 생성된 코루틴 작업입니다. 지정된 코드 블록을 실행하고 이 블록이 완료될 때 완료된다.
- Job() 팩토리 함수로 생성된 CompletableJob입니다. CompletableJob은 CompletableJob.complete를 호출하여 완료된다.
추가적으로 Job의 Lifecycle에 대해 알아보기 위해 Kotlin Coroutine 책을 보고 정리한 블로그를 참고했다.
Job 의 LifeCycle 은 위와 같다.
- 대부분의 Coroutine 들은“Active” 상태로 시작하지만, Job 은 특이하게도 “NEW”*라는 상태로 시작한다. 그래서*Lazily 하게 실행될 수 있다.
- Job 이 실행되게 되면 상태는 Active 로 바뀌게 된다. 여기서 Job 이 성공적으로 완료됐다면 Completing 상태로 돌아가고, Children 이 끝나는 걸 대기한다. 모든 Children이 종료되면, Job 은 Completed 상태가 된다.
- 만약Active 상태 이후로 실패되거나 취소 되면, Cancelling 상태로 변경된다.
- 보통 Network I/O 로직에 Coroutine을 사용하는데 Cacelling 상태일 경우 Session을 끊거나, Resource를 반환하거나 하는 일들을 수행하면 좋다고 한다. 이것마저 끝나면 “Cancelled” 상태가 된다.
Lazily Start
- 아까 위에서 설명했듯이New 라는 State 가 존재하기에 Job 은 Lazily 하게 실행할 수 있다.
// launch started lazily is in New state val lazyJob = launch(start = CoroutineStart.LAZY) { delay(1000) } println(lazyJob) // LazyStandaloneCoroutine{New}@ADD // we need to start it, to make it active lazyJob.start() println(lazyJob) // LazyStandaloneCoroutine{Active}@ADD lazyJob.join() // (1 sec) println(lazyJob) //LazyStandaloneCoroutine{Completed}@ADD 출처: <https://devroach.tistory.com/137> [Rlog:티스토리]
사용예시
- Job을 기본 노트앱의 ViewModel에서 현재 저장된 메모들을 받아오는 getNotes 함수에 적용시켜 보았다.
- getNotes 함수를 호출할 때 마다 이전 코루틴을 취소하는 코드를 넣어주었다.
- 결과적으로 코루틴 중복 실행을 방지해 메모리 누수를 함께 방지되는 이점이 생겼다고 볼 수 있다.
기존코드
private fun getNotes(noteOrder: NoteOrder){
noteUseCases.getNoteUseCase(noteOrder)
.map { notes ->
NoteState(
notes = notes,
noteOrder = noteOrder
)
}.onEach { newNoteState ->
_state.value = newNoteState
}.launchIn(viewModelScope)
}
변경코드
private var getNotesJob : Job? = null
private fun getNotes(noteOrder: NoteOrder){
getNotesJob?.cancel()
getNotesJob = noteUseCases.getNoteUseCase(noteOrder)
.map { notes ->
NoteState(
notes = notes,
noteOrder = noteOrder
)
}.onEach { newNoteState ->
_state.value = newNoteState
}.launchIn(viewModelScope)
}