Coroutines/Coroutine Basics

[Coroutine] 7. Coroutine Job 상태 관리하기 : New, Active, Completed, Cancelling, Cancelled

반응형

Job의 상태

Job의 상태는 생성, 실행 중, 실행 완료, 취소 중, 취소 완료 총 5가지이다.

 

그림1. Job 상태의 종류

  • 생성(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은 실행에 성공하여 종료될 것이다.

그림2. Job 성공

 

하지만 만약 서버에서 결과를 주지 않는다면 <그림3>과 같이 우리는 계속해서 응답을 기다리고 있어야 한다. 

그림3. 응답 대기 상태

이러한 상황에서는 일정 시간 이후에 Job을 취소하는 작업이 필요하다. 또한 Job을 취소했을 때 생기는 Exception에 대한 Handling이 필요하다.  이제부터 Job을 취소하는 방법과 Exception을 Handling하는 방법을 알아보자.

 

 

cancel()을 이용한 Job의 취소

job을 취소하는 것은 간단하다. 아래의 코드와 같이 cancel()을 이용해 job을 취소할 수 있다.

suspend fun main() {
    val job = CoroutineScope(Dispatchers.IO).launch {
        delay(1000)
    }

    job.cancel()

    delay(3000)
}

하지만 그냥 취소를 해주면 원인을 알 수 없으니 취소가 된 원인을 인자로 넣어보자.

 

 

cancel()에 cancel된 원인 넣고 원인 출력하기

cancel()에 두가지 인자 message : Stringcause : Throwable을 넘기는 것으로 취소의 원인을 알릴 수 있다.

또한 Job에 getCancellationException()메서드를 사용함으로써 취소의 원인을 알 수 있게 된다. 

suspend fun main() {
    val job = CoroutineScope(Dispatchers.IO).launch {
        delay(1000)
    }

    job.cancel("Job Cancelled by User", InterruptedException("Cancelled Forcibly")) // cacnel 원인 넘기기

    println(job.getCancellationException()) // cancel 원인 출력

    delay(3000)
}

 

위 코드의 출력은 아래와 같다. cancel시 넘겨지는 Exception의 종류는 CancellationException으로 고정된다. 위에서는 InterruptedException을 넘겼지만, 출력되는 것은 CacellationException이며 cancel시 넘긴 Throwable은 반영되지 않는다.

java.util.concurrent.CancellationException: Job Cancelled by User

Process finished with exit code 0

 

 

cancel되었을 때 동작 Handling하기

cancel된 원인을 Handling하는 것은 간단하다. Job가 취소 완료 될 때 invokeOnCompletion 내의 메서드가 호출이 되는데 이를 활용하여 애러를 핸들링한다.

suspend fun main() {
    val job = CoroutineScope(Dispatchers.IO).launch {
        delay(1000)
    }

    //취소된 원인 handling
    job.invokeOnCompletion { throwable ->
        println(throwable)
    } 

    job.cancel("Job Cancelled by User", InterruptedException("Cancelled Forcibly")) // cacnel 원인 넘기기

    delay(3000)
}
java.util.concurrent.CancellationException: Job Cancelled by User

Process finished with exit code 0

위 코드에서 job.invokeOnCompletion 에서 throwable을 받고 해당 throwable을 출력할 수 있다. throwable은 InterrupedException을 넘기더라도 Cancellation Exception으로 잡힌다.

 

그런데 문제는 invokeOnCompletion은 Job이 취소완료 되었을 때 뿐만 아니라, 실행 완료 되었을 때도 실행된다.

한 번 아래의 코드를 실행해보자

suspend fun main() {
    val job = CoroutineScope(Dispatchers.IO).launch {
        delay(1000)
    }

    //취소된 원인 handling
    job.invokeOnCompletion { throwable ->
        println(throwable)
    }

    delay(3000)
}
null

Process finished with exit code 0

취소 없이 실행이 완료되자 throwable에 null이 나온다. 따라서 이 부분은 다음과 같이 Handling 해주어야 한다.

suspend fun main() {
    val job = CoroutineScope(Dispatchers.IO).launch {
        delay(1000)
    }

    //취소된 원인 handling
    job.invokeOnCompletion { throwable ->
        when(throwable){
            is CancellationException -> println("Cancelled")
            null -> println("Completed with no error")
        }
    }

    delay(3000)
}

Job이 실행 완료되었을 때랑 취소 완료되었을 때 모두 invokeOnCompletion내의 메서드가 호출되는 이유는 Job의 상태 변수와 관계가 있다. 다음 글에서 이에 대해 알아보도록 하자.

반응형