이 섹션은 일시 중단 함수를 구성하기 위한 다양한 접근 방식을 다룬다.
기본적인 순차 처리
일종의 원격 서비스 호출이나 계산 같은 두 유용한 일시 중단 함수들이 서로 다른 위치에 정의되어 있다고 가정해보자. 이들은 유용한척 하지만 실제로는 이 예제의 목적을 위해 1초간 delay가 일어난다.
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // pretend we are doing something useful here
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // pretend we are doing something useful here, too
return 29
}
먼저 doSomethingUsefulOne을 호출하고 doSomethingUsefulTwo을 호출한 다음 결과의 합계를 계산해야 하는 경우 이들을 순차적으로 실행되도록 하기 위해서 어떤 것을 해야할까? 이런 작업은 첫 째 함수의 결과를 사용해 둘 째 함수를 호출해야 하는지 혹은 어떻게 호출 할지를 결정해야 할 때 사용된다.
Coroutine 코드는 일반적인 코드와 같이 기본적으로 순차적이기 때문에, 일반적인 순차 호출을 사용한다. 다음 예제는 두 일시 중단 함수들을 실행하는데 걸리는 총 시간을 측정하여 보여준다.
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이 코드는 다음의 결과를 출력한다.
The answer is 42
Completed in 2017 ms
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Composing suspending functions - Sequential by default
원문 최종 수정 : 2022년 6월 27일
async를 사용한 동시성
만약 doSomethingUsefulOne과 doSomethingUsefulTwo의 실행 사이에 종속성이 없고, 이 둘을 동시에 실행함으로써 응답을 더 빨리 얻고 싶다면 어떻게 해야할까? 여기에서 async가 사용될 수 있다.
개념적으로 async는 launch와 같다. async는 다른 스레드들과 동시에 동작하는 별도의 경량 Thread인 Coroutine을 시작한다. 다른 점은 launch는 결과값을 전달하지 않는 Job을 return 하지만, async는 나중에 결과값을 반환할 것을 약속하는 경량이고 Blocking을 하지 않는 Future*1인 Deffered를 반환한다는 점이다. Deferred에 대해 .await() 함수를 사용해 결과값을 얻을 수 있지만, Deffered 또한 작업이라 필요할 때 취소될 수 있다.
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
📌 전체 코드는 이곳에서 확인할 수 있습니다.
위 코드는 다음의 결과를 출력한다.
The answer is 42
Completed in 1017 ms
두 Coroutine들이 동시에 실행되기 때문에 두 배 정도 빠른 것을 볼 수 있다. Coroutines의 동시성은 언제나 명시적이다.
*1. Future이란 프로그래밍 언어에서 프로그램 실행을 동기화하려고 쓰는 구조체의 일종으로, 읽기 전용 플레이스 홀더(값)이다. Future의 값은 Promise(값을 쓰는 함수)에 의해 설정되며, 하나의 Future에 대해서는 한 번만 값이 설정될 수 있다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Composing suspending functions - Concurrent using async
원문 최종 수정 : 2022년 6월 27일
async lazy하게 시작하기
선택적으로 첫 파라미터 값을 CoroutineStart.LAZY 로 설정함으로써 async를 lazy하게 만들 수 있다. 이 모드에서는 Coroutine의 결과값이 await에 의해 필요해지거나, Job의 start 함수가 실행될 때 시작된다. 다음 예를 실행해보자.
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
// some computation
one.start() // start the first one
two.start() // start the second one
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이는 다음의 결과를 생성한다.
The answer is 42
Completed in 1017 ms
여기에는 두 개의 Coroutine이 정의되어 있지만 이전 예제*1와 같이 실행되지 않으며, 프로그래머에게 start를 사용하여 언제 시작할 것인지에 대한 제어 권한이 주어진다. 먼저 one을 실행한 다음 two를 시작하며, 각 Coroutine들이 끝날 때까지 기다린다.
await은 Coroutine을 시작하고 완료를 기다리도록 하기 때문에, 개별 Coroutine들에서 start를 호출하지 않고 println 함수 내부에서 await을 호출하면 순차 처리*2가 된다. 이는 지연 처리를 위한 의도된 유즈케이스가 아니다. async(start = CoroutineStart.LAZY)는 값의 연산을 위한 계산이 일시 중단 함수를 포함할 때 표준 lazy 함수를 대체한다.
아래의 내용들은 모두 독자의 이해를 위해 번역자가 추가한 내용입니다.
*1. 이전 예제 코드는 다음과 같다.
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
이 예제에서 async는 선언되자마자 실행된다.
*2. 다음과 같이 println 내부에서
fun main() = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
다음의 결과가 출력된다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Composing suspending functions - Lazily started async
원문 최종 수정 : 2022년 6월 27일
목차로 돌아가기