비동기 함수를 두 그룹으로 나눠볼 수 있음
결과가 없는 비동기 함수:
일반적인 시나리오로는 로그에 기록하고 분석 데이터를 전송하는 것과 같은 백그라운드 작업을 들 수 있음 완료 여부를 모니터링할 수 있지만 결과를 갖지 않는 백그라운드 작업이 이런 유형에 속함
결과를 반환하는 비동기 함수:
예를 들어 비동기 함수가 웹 서비스에서 정보를 가져올 때 거의 대부분 해당 함수를 사용해 정보를 반환하고자 할 것
두 가지중 어떤 경우이건 해당 작업에 접근하고 예외가 발생하면 그에 대응하거나, 해당 작업이 더 이상 필요하지 않을 때 취소함
fun main(args:Array<String>) = runBlocking { val job = GlobalScope.launch { // Do background task here } } 다음과 같이 Job() 팩토리 함수를 사용할 수도 있다. fun main(args:Array<String>) = runBlocking { val job = Job() }
⚡
Job은 인터페이스로, launch()와 Job()은 모두 JobSupport의 구현체를 반환한다.
앞으로 보게 될 텐데 JobSupport는 잡을 확장한 인터페이스인 Job.Deferred의 여러 구현체의 기반이다.
fun main(args:Array<String>) = runBlocking { val job = GlobalScope.launch { TODO("Not Implemented") } delay(500) /*delay()를 사용해 충분한 시간 동안 앱을 실행하게 해서 예외가 발생하게 했다. 잡이 완료 될 때까지 대기하지 않더라도 예외가 전파된다는 것을 보여주기 위해 의도적으로 join()을 사용하지 않았다. */ }
↪ 현재 스레드의 포착되지 않은 예외 처리기에 예외가 전파
⚡
기본적으로 Job은 생성되는 즉시 시작된다.
이것은 Job이 launch()로 생성되거나 Job()으로 생성될 때 발생한다.
Job을 생성할 때 시작하지 않게 하는 것도 가능하다.
다이어그램의 다섯 가지 상태에 대해
fun main(args:Array<String>) = runBlocking { GlobalScope.launch(start = CoroutineStart.LAZY) { TODO("Not implemented yet!") } delay(500) }
↪ 코드를 실행하면 오류가 출력되지 ❌
작업은 생성됐지만 시작된 적이 없으므로 예외가 발생 ❌
fun main(args:Array<String>) { val job = GlobalScope.launch(start = CoroutineStart.LAZY) { delay(3000) } job.start() }
↪ job.start()가 호출될 때 실행을 일시 중단하지 않으므로 앱이 job이 완료되는 것을 기다리지 않고 실행을 끝냄
⚡
start()는 실행을 일시 중단하지 않으므로 일시 중단 함수나 코루틴에서 호출할 필요가 없다. 앱의 어느 부분에서도 호출할 수 있다.
fun main(args:Array<String>) { val job = GlobalScope.launch(start = CoroutineStart.LAZY) { delay(3000) } job.join() }
↪ join()을 사용하면 앱이 job을 완료할 때까지 대기
⚡중요⚡
join()은 실행을 일시 중단할 수 있으므로 코루틴 또는 일시 중단 함수에서 호출해야 한다. 이를 위해 runBlocking()이 사용되고 있음에 유의
fun main(args:Array<String>) { val job = GlobalScope.launch { // Do Some work here delay(5000) } delay(2000) job.cancel() }
↪ 잡 실행은 2초 후에 취소
fun main(args:Array<String>) { val job = GlobalScope.launch { // Do Some work here delay(5000) } delay(2000) // cancel with a cause job.cancel(cause = Exception("Timeout!") // kotlinx.coroutines 1.0.0-RC1 에서 // job.cancel(cause)는 deprecated 됐다. }
fun main(args:Array<String>) { val job = GlobalScope.launch { // Do Some work here delay(5000) } delay(2000) // cancel job.cancel(cause = CancellationException("Tired of waiting")) val cancellation = job.getCancelltaionException() println(cancellation.message) }
↪ 취소된 잡과 예외로 인해 실패한 잡을 구별하기 위해 다음과 같이
CoroutineExceptionHandler를 설정해 취소 작업을 처리하는 것이 좋음👍
fun main(args:Array<String>) = runBlockin { val exceptionHandler = CoroutineExceptionHandler { _: CoroutineContext, throwble: Throwble -> println("Job cancelled due to ${throwable.message}") } GlobalScope.launch(exceptionHandler) { TODO("Not implemented yet!") } delay(2000) } 다음과 같이 invokeOnCompletion()을 사용할 수도 있다. fun main(args:Array<String>) = runBlockin { GlobalScope.launch { TODO("Not implemented yet!") }.invodeOnCompletion { cause -> cause?.let { println("Job cancelled due to ${it.message}") } } delay(2000) }
isAcitive: 잡이 활성 상태인지 여부. 잡이 일시 중지인 경우 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 |
잡을 설명하는 문서에는 완료중(completing)이라고 하는 내부 상태가 있다.
이 상태는 내부 상태이며, 시그니처는 활성 상태와 유사하다는 점을 고려할 때 개별 상태로는 다루지 않는다.
디퍼드(Deferred, 지연)는 결과를 갖는 비동기 작업을 수행하기 위해 잡을 확장
기본적인 컨셉은 연산이 객체를 반환, 객체는 비동기 작업이 완료될 때까지 비어 있다는 것
디퍼드와 그 상태의 라이프 사이클은 잡과 비슷
디퍼드를 만들려면 async를 사용할 수 있음
fun main(args:Array<String>) = runBlockin { val headlinesTask = GlobalScope.async { getHeadlines() } headlinesTask.await() } 또는 CompletableDeferred의 생성자를 사용할 수 있다. val articlesTask = CompletableDeferred<List<Article>>()
fun main(args:Array<String>) = runBlockin { val deferred = GlobalScope.async { TODO("Not implemented yet!") } // Wait for it to fail delay(2000) } 앞의 예제는 지연된 실패를 갖지만 예외를 전파 X 예외를 쉽게 전파할 수 있는 방법 fun main(args:Array<String>) = runBlockin { val deferred = GlobalScope.async { TODO("Not implemented yet!") } // Let it fail deferred.await() } // 앞의 코드와 다르게 이 코드는 예외를 전파하고 앱을 중단시킬 것이다.
디퍼드의 실행이 코드 흐름의 필수적인 부분임을 나타내는 것이기 때문에
await()을 호출하는 방식으로 설계
fun main(args:Array<String>) = runBlockin { val deferred = GlobalScope.async { TODO("Not implemented yet!") } try { deferred.await() } catch (throwable: Throwable) { println("Deferred cancelled due to ${throwable.message}") } }
이 장의 나머지 부분에서는 잡과 디퍼드를 모두 잡으로 표기
잡이 베이스 인터페이스이기 때문에 별도로 명시하지 않는 이상 모두 디퍼드에도 적용된다.
fun main(args: Array<String>) = runBlocking { val time = measureTimeMillis { val job = GlobalScope.launch { delay(2000) } // Wait for it to complete once job.join() // Restart the Job job.start() job.join() } println("Took $time ms") }
↪ 코드는 2초 동안 실행을 일시 중단하는 잡을 만듦
처음 호출한 job.join()이 완료되면 잡을 다시 시작하기 위해 start() 함수가 호출되고, 두 번째 join() 을 호출해서 두 번째 실행이 끝날 때까지 대기
전체 실행 시간을 측정하고 time 변수에 저장한다.
총 실행에는 약 2초가 걸렸으므로 잡이 한 번만 실행됐음을 보여줌
완료된 잡에서 start()를 호출해 다시 시작했다면 총 실행 시간은 약 4초가 될 것
이전에 기술한
일단 잡이 특정 상태에 도달하면 이전 상태로 되돌아 가지 않는다.
과 일치한다.
잡은 완료됨(Completed) 상태에 도달했으므로 start()를 호출해도 아무런 변화가 없다.
모니터링할 때 잡의 다양한 상태와 현재의 상태를 산출하는 방법을 아는 것이 중요
잡(Job) 은 아무것도 반환하지 않는 백그라운드 작업에 사용
디퍼드(Deferred) 는 백그라운드 작업이 수신하려는 것을 반활할 때 사용
잡은 다양한 상태값을 갖음
-> 생성, 활성, 취소 중, 취소됨 및 완료됨
잡의 현재 상태를 파악하기 위해 isActive, isCancelled 및 is Completed 속성 사용
디퍼드는 잡을 확장해 무언가를 반환할 가능성을 높임
디퍼드가 가질 수 있는 상태는 잡의 상태와 같음
잡의 상태는 앞으로만 이동할 수 있음. 이전 상태로 되돌릴 수 없음❌❌
최종 상태는 잡이 이동할 수 없는 상태 중 하나
잡의 최종 상태는 취소됨 및 완료됨
Join() 을 사용해 디퍼드가 대기된 경우, 예외가 전파되지 않도록 값을 읽기 전에 취소됐는지 여부를 확인해야 함
항상 잡에 예외를 기록하거나 표시하자🔥