[Coroutine] 코루틴 빌더(2)
코틀린 코루틴을 공부하며 정리한 글입니다.
혼자 공부하고 정리한 내용이며, 틀린 부분은 지적해주시면 감사드리겠습니다 😀
코루틴 빌더
코루틴 빌더에 대한 자세한 내용은 이전 포스팅을 참고해주세요!
동기 / 비동기
우선, 비동기에 대한 정의를 먼저 확인해보자.
컴퓨터 프로그래밍에서 async/await 패턴은 비동기, 비차단 기능이 일반 동기 기능과 유사한 방식으로 구조화되도록 하는 많은 프로그래밍 언어의 구문 기능 구현이다. - (위키백과)
동기와 비동기의 차이가 무엇인지 코드로 간단하게 한 번 살펴보자.
suspend fun main() {
val result = measureTimeMillis {
doSomething1()
doSomething2()
}
println("total = ${result.toDuration(DurationUnit.MILLISECONDS)}")
}
suspend fun doSomething1() {
println("Do something1")
delay(1000)
}
suspend fun doSomething2() {
println("Do something2")
delay(1000)
}
Do something1
Do something2
total = 2.02s
위 함수는 일반적인 함수와 같이 동기적으로 실행된다. 즉, doSomething1
을 호출하고, 해당 함수를 끝마친 뒤에 doSomething2
가 실행되어, 2초에 걸쳐서 프로그램이 종료된다. 그렇다면 위 코드를 비동기로 바꾸면 어떤 결과가 나오게 될까?
suspend fun main() = runBlocking {
val result = measureTimeMillis {
val r1 = async { doSomething1() }
val r2 = async { doSomething2() }
println("result = ${r1.await()} ${r2.await()}")
}
println("total = ${result.toDuration(DurationUnit.MILLISECONDS)}")
}
suspend fun doSomething1(): String {
delay(1000)
return "hello"
}
suspend fun doSomething2(): String {
delay(1000)
return "world"
}
result = hello world
total = 1.025s
시간을 보면 알 수 있듯, 2초가 걸리던 main
함수가 1초로 단축되었다. 이렇듯, 비동기는 작업을 다른 스레드에게 맡기고, 본 스레드의 업무를 계속해서 진행하는 것이다. 즉, 이전 포스팅에 나왔던, launch
와 비슷한 느낌인 것이다.
Async
앞서 작성한 코드를 다시 가져와서 살펴보자.
suspend fun main() = runBlocking {
val result = measureTimeMillis {
val r1 = async { doSomething1() }
val r2 = async { doSomething2() }
println("result = ${r1.await()} ${r2.await()}")
}
println("total = ${result.toDuration(DurationUnit.MILLISECONDS)}")
}
suspend fun doSomething1(): String {
delay(1000)
return "hello"
}
suspend fun doSomething2(): String {
delay(1000)
return "world"
}
우선, 코드만 보고 알 수 있는 사실을 하나씩 확인해보자.
앞서 async
블록은 launch
와 비슷하다고 했는데, launch
와 제각각으로 실행된다는 것은 비슷하지만, launch
블록은 값을 반환하지 않는다. 반면에 async
는 코드에서 볼 수 있는 것과 같이 블록 내부에 작성한 값을 반환하게 된다. 코틀린의 let
스코프와 비슷하게 블록의 마지막 줄을 반환한다.
약간의 차이가 있다면, async
는 값을 반환할 때, Deferred<T>
라는 타입으로 감싼 뒤에 반환하게 된다. 즉, r1
을 그대로 쓸 경우 다음과 같이 엉뚱한 값을 반환받게 되는 것이다.
DeferredCoroutine{Active}@6fadae5d
그냥 비동기로 실행하기만 할거면 launch
를 사용하겠지만, async
의 목적은 비동기로 실행하되, 값을 반환받기 위한 것이기 때문에, Deferred
로 감싼 뒤에, 비동기로 실행한 해당 함수가 완료되었는지, 확인하고 값을 꺼내오는 것이 바로 r1.await()
이다.
때문에 async
빌더는 시간이 오래 걸리지만, 값을 꼭 받아와야지 다른 함수를 사용할 수 있는 경우에 많이 사용된다.
정리
회사 사내 서비스로 배포 관련 시스템을 개발할 때, async
를 사용해, 클라이언트가 응답을 기다리는 시간을 절반 이상으로 줄인 경험이 있다. 당연히 다음과 같은 형태로 사용한다면, async
를 사용하는 의미가 없다.
suspend fun deploy() {
val p1Result = async { part1() }
p1Result.await()
}
suspend fun part1(){
delay(3000)
}
이렇게 async
를 호출하자마자 바로 await()
을 사용하면 해당 함수를 비동기로 실행시킨 의미가 없으니, 구조를 잘 파악해서 사용하는 것이 좋다.