Job의 상태
Job의 상태는 생성, 실행 중, 실행 완료, 취소 중, 취소 완료 총 5가지이다.
- 생성(New) : Job이 생성된다.
- 실행 중(Active) : Job이 실행 중이다.
- 실행 완료(Completed) : Job의 실행이 완료되었다.
- 취소 중(Cancelling) : Job이 취소되는 중이다. Job이 취소되면 리소스 반환 등의 작업을 해야 하기 때문에 취소 중 상태가 있다.
- 취소 완료(Cancelled) : Job의 취소가 완료되었다.
앞선 글에서 다룬 내용 : Job의 생성과 실행
앞선 글에서 우리는 Job의 생성과 실행을 다루었다.
- launch를 통한 Job의 생성 및 실행
- launch에 CoroutineStart.LAZY 옵션을 추가하여 Job을 바로 실행되지 않게 만들기
- CoroutineStart.LAZY로 생성된 Job을 시작하는 방법 : start(),join()
위와 같은 과정을 거쳐 생성 및 실행된 Job은 일반적으로 작업이 끝나면 실행완료 상태가 된 다음 종료된다.
실행 완료가 되지 않는다면?
하지만, Job은 항상 실행에 성공하여 실행 완료 상태가 되지는 않는다. 다양한 변수로 인해 중간에 취소되어야 할 수 있다.
예를 들어보자. 네트워크를 통해 유저의 정보를 달라는 요청을 했을 때, 우리는 요청의 결과를 기다려야한다. <그림2>과 같이 서버에서 요청이 성공되거나 거부되었다는 메세지를 보내주면 Job은 실행에 성공하여 종료될 것이다.
하지만 만약 서버에서 결과를 주지 않는다면 <그림3>과 같이 우리는 계속해서 응답을 기다리고 있어야 한다.
이러한 상황에서는 일정 시간 이후에 Job을 취소하는 작업이 필요하다. 또한 Job을 취소했을 때 생기는 Exception에 대한 Handling이 필요하다. 이제부터 Job을 취소하는 방법과 Exception을 Handling하는 방법을 알아보자.
cancel()을 이용한 Job의 취소
job을 취소하는 것은 간단하다. 아래의 코드와 같이 cancel()을 이용해 job을 취소할 수 있다.
fun main() = runBlocking<Unit> {
val job = launch(Dispatchers.IO) {
delay(1000)
}
job.cancel()
}
하지만 그냥 취소를 해주면 원인을 알 수 없으니 취소가 된 원인을 인자로 넣어보자.
cancel()에 cancel된 원인 넣고 원인 출력하기
cancel()에 두가지 인자 message : String과 cause : Throwable을 넘기는 것으로 취소의 원인을 알릴 수 있다.
또한 Job에 getCancellationException()메서드를 사용함으로써 취소의 원인을 알 수 있게 된다.
fun main() = runBlocking<Unit> {
val job = launch(Dispatchers.IO) {
delay(1000)
}
job.cancel("Job Cancelled by User", InterruptedException("Cancelled Forcibly"))
println(job.getCancellationException()) // cancel 원인 출력
}
위 코드의 출력은 아래와 같다. cancel시 넘겨지는 Exception의 종류는 CancellationException으로 고정된다. 위에서는 InterruptedException을 넘겼지만, 출력되는 것은 CacellationException이며 cancel시 넘긴 Throwable은 반영되지 않는다.
java.util.concurrent.CancellationException: Job Cancelled by User
Process finished with exit code 0
cancel 된 원인 출력하기
cancel된 원인을 출력하는 방법은 간단하다. Job가 취소 완료 될 때 invokeOnCompletion 내의 메서드가 호출이 되는데 이를 사용해 취소된 원인을 출력할 수 있다.
fun main() = runBlocking<Unit> {
val job = launch(Dispatchers.IO) {
delay(1000)
}
//취소된 원인 출력
job.invokeOnCompletion { throwable ->
println(throwable)
}
job.cancel("Job Cancelled by User", InterruptedException("Cancelled Forcibly"))
}
java.util.concurrent.CancellationException: Job Cancelled by User
Process finished with exit code 0
위 코드에서 job.invokeOnCompletion 에서 throwable을 받고 해당 throwable을 출력할 수 있다. throwable은 InterrupedException을 넘기더라도 Cancellation Exception으로 잡힌다.
그런데 문제는 invokeOnCompletion은 Job이 취소완료 되었을 때 뿐만 아니라, 실행 완료 되었을 때도 실행된다.
한 번 아래의 코드를 실행해보자
fun main() = runBlocking<Unit> {
val job = launch(Dispatchers.IO) {
delay(1000)
}
//취소된 원인 출력
job.invokeOnCompletion { throwable ->
println(throwable)
}
}
null
Process finished with exit code 0
취소 없이 실행이 완료되자 throwable에 null이 나온다. 따라서 이 부분은 다음과 같이 Handling 해주어야 한다.
fun main() = runBlocking<Unit> {
val job = launch(Dispatchers.IO) {
delay(1000)
}
job.invokeOnCompletion { throwable ->
when(throwable){
is CancellationException -> println("Cancelled")
null -> println("Completed with no error")
}
}
}
그러면 실행 시 다음과 같은 결과를 볼 수 있다.
Completed with no error
Process finished with exit code 0
Job이 실행 완료되었을 때랑 취소 완료되었을 때 모두 invokeOnCompletion내의 메서드가 호출되는 이유는 Job의 상태 변수와 관계가 있다. 다음 글에서 이에 대해 알아보도록 하자.