3장 라이프 사이클과 에러 핸들링

코틀린 동시성 프로그래밍 3장을 요약한 내용입니다.

잡과 디퍼드

비동기 함수를 담과 같이 두 그룹으로 나눠 볼 수 있다.

  • 결과가 없는 비동기 함수 : 일반적인 시나리오는 로그에 기록하고 분석 데이터를 전송하는 것과 같은 백그라운드 작업을 들 수 있다. 완료 여부를 모니터링할 수 있지만 결과를 갖지 않는 백그라운드 작업이 이런 유형에 속한다.

  • 결과를 반환하는 비동기 함수 : 예를 들어 비동기 함수가 웹 서비스에서 정보를 가져올 때 거의 대부분 해당 함수를 사용해 정보를 반환하고자 할 것이다.

잡은 파이어-앤-포겟(fire and forget)작업이다. 한 번 시작된 작업은 예외가 발생하지 않는한 대기하지 않는다.

fun main(args: Array<String>) = runBlocking {
		val job = GlobalScope.launch {
				// Do background task here
		}
} 

예외 처리

기본적으로 잡 내부에서 발생하는 예외는 잡을 생성한 곳까지 전파된다. 잡이 완료되기를 기다리지 않아도 발생한다.

fun main(args: Array<String>) = runBlocking {
		val job = GlobalScope.launch {
				// TODO("Not Implemented!")
		}

		delay(500)
} 

이렇게 하면 현재 스레드의 포착되지 않은 예외 처리기에 예외가 전파된다.

라이프 사이클

다음은 잡의 라이프 사이클을 보여주는 다이어그램이다.

  • New(생성) : 존재하지만 아직 실행되지 않는 잡

  • Active(활성) : 실행 중인 잡. 일시 중단된 잡도 활성으로 간주한다.

  • Completed(완료 됨) : 잡이 더 이상 실행되지 않는 경우

  • Canceling(취소 중) : 실행 중인 잡에서 cancel()이 호출되면 취소가 안료될 때까지 시간이 걸리기도 한다. 이것은 활성과 취소 사이의 중간 상태다.

  • Cancelled(취소 됨) : 취소로 인해 실행이 안료된 잡. 취소된 잡도 완료로 간줄될 수 있다.

생성

잡은 기본적으로 launch()나 Job()을 사용해 생성될 때 자동으로 시작한다. 잡을 생성할 때 자동으로 시작되지 않게 하려면 CoroutineStart.LAZY를 사용해야 한다.

fun main(args: Array<String>) = runBlocking {
		val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
				// TODO("Not Implemented!")
		}

		delay(500)
} 

활성

생성 상태에 있는 잡은 다양한 방법으로 시작할 수 있지만 일반적으로 start()나 join()을 호출해서 실행하는데, 둘의 차이점은 전자의 경우 잡이 완료될 때까지 기다리지 않고 잡을 시작하는 반면 후자는 잡이 완료될 때까지 실행을 일시 중단한다는 점이다.

fun main(args: Array<String>) = runBlocking {
		val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
				// TODO("Not Implemented!")
		}

		job.start() // start는 실행을 중단하지 않으므로 suspend 함수나 코루틴에서 호출할 필요가 없다. 
		job.join() // join은 실행을 일시 중단할 수 있으므로 코루틴 또는 일시 중단 함수에 호출해야 한다. 
} 

취소 중

취소 요청을 받은 활성 잡은 취소 중이라고 하는 스테이징 상태로 들어갈 수 있다.

fun main(args: Array<String>) = runBlocking {
		val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
				// TODO("Not Implemented!")
		}

		job.cancel()
		job.cancel(cause = Exception("Timeout!")) // 예외가 취소의 원인일 때는 원인을 같이 제공해 주면 나중에 찾아볼 수 있다.  
} 

취소 됨

취소 또는 처리되지 않은 예외로 인해 실행이 종료된 잡은 취소됨으로 간주된다. 잡이 취소되면, getCancellationException() 함수를 통해 취소에 대한 정보를 얻을 수 있다.

fun main(args: Array<String>) = runBlocking {
		val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
				// TODO("Not Implemented!")
		}

		job.cancel()
		
		val cancellation = job.getCancellationException()
		printf(cancellation.message)
}

취소된 잡과 예외로 인해 실패한 잡을 구별하기 위해 다음과 같이 CoroutineExceptionHandler를 설정해 취소 작업을 처리하는 것이 좋다.

fun main(args: Array<String>) = runBlocking {
		val exceptionHandler = CoroutineExceptionHandler {
				_: CoroutineContext, throwable: Throable ->
				println("Job cancelled due to ${throwable.message}")	
		}

		GlobalScope.launch(exceptionHandler) {
				// TODO("Not Implemented!")
		}

		delay(2000)
}

다음과 같이 invokeOnCompletion()을 사용할 수도 있다.

fun main(args: Array<String>) = runBlocking {
		GlobalScope.launch {
				// TODO("Not Implemented!")
		}.invokeOnCompletion { cause ->
				case?.let {
						println("Job cancelled due to ${it.message}")
				}
		}

		delay(2000)
}

잡의 현재 상태 확인

잡에는 상태가 많아서 외부에서 현재 상태를 파악하는 방법을 알아야 한다.

  • isActive : 잡이 활성 상태인지 여부. 잡이 일지 중지인 경우도 true를 반환한다.

  • isCompleted : 잡이 실행을 완료했는지 여부

  • isCancelled : 잡 취소 여부. 취소가 요청되면 즉시 true가 된다.

속성 정보

상태(State)

isActive

isCompleted

isCancelled

생성됨(Created)

false

false

false

활성(Active)

true

false

false

취소중(Cancelling)

false

false

true

취소됨(Cancelled)

false

true

true

완료됨(Completed)

false

true

false

디퍼드

디퍼드(Deffered)는 결과를 갖는 비동기 작업을 수행하기 위해 잡을 확장한다.

다른 언어에서는 Future 또는 Promise라고 하는 것의 코틀린 구현체가 디퍼드다. 기본적인 콘셉트는 연산이 객체를 반환할 것이며, 객체는 비동기 작업이 완료될 때까지 비어 있다는 것이다.

fun main(args: Array<String>) = runBlocking {
		val headlinesTask = GlobalScope.async {
				getHealines
		}

		headlinesTask.await()
}

예외 처리

순수한 잡과 달리 디퍼드는 처리되지 않은 예외를 자동으로 전파하지 않는다. 디퍼드의 결과를 대기할 것으로 예상하기 때문에 이런 방식을 사용했다.

fun main(args: Array<String>) = runBlocking {
		val deffered = GlobalScope.async {
				TODO("Not implemented yet!")
		}
		try {
				headlinesTask.await()
		} catch (throwable: Throable) {
				println("Deferred cancelled due to ${throwable.message}")
		}
}

요약

  • 잡(Job)은 아무것도 반환하지 않는 백그라운드 작업에 사용된다.

  • 디퍼드(Deferred)는 백그라운드 작업이 수신하려는 것을 반환할 때 사용된다.

  • 잡은 다양한 상태값을 갖는다: 생성(New), 활성(Active), 취소 중(Canceling), 취소됨(Cancelled) 및 완료됨(Completed)

  • 잡의 현재 상태를 파악하기 위해 isActive, isCancelled 및 isCompleted 속성을 사용할 수 있다.

  • 디퍼드는 잡을 확장해 무언가를 반환할 가능성을 높인다.

  • 디퍼드가 가질 수 있는 상태는 잡의 상태와 같다

  • 잡의 상태는 앞으로만 이동할 수 있다. 이전 상태로 되돌릴 수 없다.

  • 최종 상태(final states)는 잡이 이동할 수 없는 상태 중 하나다.

  • 잡의 최종 상태는 취소됨(Cancelled) 및 완료됨(Completed)이다.

  • join()을 사용해 디퍼드가 대기된 경우, 예외가 전파되지 않도록 값을 읽기 전에 취소됐는지 여부를 확인해야 한다.

  • 항상 잡에 예외를 기록하거나 표시하자

Last updated