이전 포스트 에서는 코루틴을 사용하기 위한 기초 개념을 알아보았습니다.
이제, 코루틴 블록 내에서 어떤 작업을 “어떻게 처리”하고 “어떠한 결과로 반환” 할것인가 하는 제어 에 관한 이야기를 다루려고 합니다.
즉, 코루틴 블록을 조합하여 동기 그리고 비동기 로 사용하는 방법입니다.
코루틴 제어를 위한 주요 키워드
- launch , async
- Job , Deferred
- runBlocking
launch() — Job
launch() 함수로 시작된 코루틴 블록은 Job 객체를 반환합니다.
val job : Job = launch {
...
}
반환받은 Job 객체로 코루틴 블록을 취소하거나, 다음 작업의 수행전 코루틴 블록이 완료 되기를 기다릴수 있습니다.
여러개의 launch 코루틴 블록을 실행할 경우 각각의 Job 객체에 대해서 join() 함수로 코루틴 블록이 완료 될때까지 다음 코드 수행을 대기할수 있습니다.
모든 Job 객체에 대해서 일일히 join() 함수를 호출하지 않고 joinAll() 함수를 이용하여 모든 launch 코루틴 블록이 완료 되기를 기다릴수도 있습니다.
joinAll(job1, job2)
또는, 다음의 예시와 같이 첫번째 launch 코루틴 블록에서 반환받은 Job 객체를 두번째 launch() 함수의 인자로 사용하면, 동일한 Job 객체로 두개의 코루틴 블록을 모두 제어 할수 있습니다.
launch() 함수로 정의된 코루틴 블록은 즉시 수행되며, 반환 받은 Job 객체는 해당 블록을 제어는 할수 있지만 코루틴 블록의 결과를 반환하지는 않습니다.
코루틴 블록의 결과 값을 반환받고 싶다면 async() 코루틴 블록을 생성합니다.
async() — Deferred
async() 함수로 시작된 코루틴 블록은 Deferred 객체를 반환합니다.
val deferred : Deferred<T> = async {
...
T // 결과값
}
이렇게 시작된 코루틴 블록은 Deferred 객체를 이용해 제어가 가능하며 동시에 코루틴 블록에서 계산된 결과값을 반환 받을수 있습니다.
여러개의 async 코루틴 블록을 실행할 경우 각각의 Deferred 객체에 대해서 await() 함수로 코루틴 블록이 완료 될때까지 다음 코드 수행을 대기할수 있습니다. await() 함수는 코루틴 블록이 완료되면 결과를 반환합니다.
각각의 Deferred 객체에 대해서 await() 함수를 호출하지 않고 awaitAll() 함수를 이용하여 모든 async 코루틴 블록이 완료 되기를 기다릴수도 있습니다.
awaitAll(deferred1, deferred2)
또는, 다음의 예시와 같이 첫번째 async 코루틴 블록에서 반환받은 Deferred 객체를 두번째 async() 함수의 인자로 사용하면, 동일한 Deferred 객체로 두개의 코루틴 블록을 모두 제어 할수 있습니다.
단, 여러개의 async 코루틴 블록에 같은 Deferred 객체를 사용할경우 await() 함수 호출시 전달되는 최종적인 결과값은 첫번째 async 코루틴 블록의 결과값 만을 전달한다는것에 주의해야 합니다.
지연 실행
launch 코루틴 블록 과 async 코루틴 블록은 모두 처리 시점을 뒤로 미룰수 있습니다.
각 코루틴 블록 함수의 start 인자에 CoroutineStart.LAZY 를 사용하면 해당 코루틴 블록은 지연 되어 실행됩니다.
val job = launch (start = CoroutineStart.LAZY) {
...
}또는val deferred = async (start = CoroutineStart.LAZY) {
...
}
launch 코루틴 블록을 지연 실행 시킬 경우 Job 클래스 의 start() 함수 를 호출하거나 join() 함수를 호출하는 시점에 launch 코드 블록이 수행됩니다.
job.start()또는job.join()
async 코루틴 블록을 지연 실행 시킬 경우 Deferred 클래스 의 start() 함수 를 호출하거나 await() 함수를 호출하는 시점에 async 코드 블록이 수행됩니다.
deferred.start()또는deferred.await()
지연된 async 코루틴 블록 의 경우 start() 함수는 async 코루틴 블록을 실행 시키지만 블록의 수행 결과를 반환하지 않습니다. 또한 await() 함수와 다르게 코루틴 블록이 완료 되는것을 기다리지 않습니다.
위 예시를 실행하면 결과는 다음과 같습니다. await() 함수를 사용했기때문에 end 는 가장 마지막에 출력됩니다.
start
lazy async 0
lazy async 1
lazy async 2
lazy async 3
lazy async 4
end
하지만 deferred.start() 로 바꾸면 출력 결과는 다음과 같습니다. end 는 start 가 출력 되자 마자 출력되고, 코루틴 블록이 수행됩니다.
start
end
lazy async 0
lazy async 1
lazy async 2
lazy async 3
lazy async 4
이 예제를 지연된 launch 코루틴 블록으로 바꾸어 실행해도 동일하게 start() 함수와 join() 함수 호출 의 결과가 다름을 알수 있습니다.
그러므로 start() 함수를 사용하여 지연 실행 하는 경우와 join() 또는 await() 함수를 사용하여 지연 실행 하는 경우, 해당 블록 이후의 코드 실행 순서에 차이가 발생할수 있음을 주의해야 합니다.
runBlocking()
runBlocking() 함수는 코드 블록이 작업을 완료 하기를 기다립니다.
runBlocking {
...
}
launch() 함수로 시작된 블록은 join() 함수로 기다립니다. async() 함수로 시작된 블록은 await() 함수로 기다립니다.
하지만 runBlocking() 함수로 시작된 블록은 아무런 추가 함수 호출 없이 해당 블록이 완료될때까지 기다릴수 있습니다.
주의해야 할것은 runBlocking 코루틴 블록이 사용하는 스레드는 현재 runBlocking() 함수가 호출된 스레드가 된다는 것입니다.
안드로이드 의 경우 runBlocking() 함수를 메인 스레드 (UI 스레드) 에서 호출하여 시간이 오래 걸리는 작업을 수행하는 경우 ANR 이 발생할 위험이 있으므로 주의해야합니다.
코루틴 에서의 작업 취소 동작 방법
코루틴의 완벽한 제어를 위해서는 작업을 기다리고, 완료된 작업의 결과를 반환 받아서 처리하는것 뿐만아니라 작업의 취소 까지도 처리할수 있어야 합니다.
제어에 사용 되는 Job 클래스 와 Deferred 클래스 에는 코루틴 블록의 작업을 취소하기 위한 cancel() 함수가 존재하고, cancel() 함수를 호출해보면 작업이 잘 취소되는것 처럼 보입니다.
하지만 정말 이것만으로도 충분 할까요? 이 방법만으로 모든 작업을 아무런 문제없이 완벽하게 마무리 할 수 있는 걸까요?
다음 포스트 에서는 코루틴 에서의 작업의 취소 가 동작하는 방식과 함께 코루틴 블록을 제어하기 위한 또 하나의 방법인 “코루틴 취소” 에 대해 알아봅니다.
감사합니다.