공식문서로 알아보는 Composing suspending functions
개요
Kotlin 공식문서를 보며 코루틴의 suspend function 작성방법에 대해 알아보면서 실습해보고자 합니다.
기본적으로 순차적으로 실행된다
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
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
}
//결과
The answer is 42
Completed in 2017 ms
suspend 키워드를 활용하여 2가지 메서드를 만들어 보고 1초의 delay를 주었습니다.
순차적으로 호출되면서 13 + 29의 결과인 42를 만들어냅니다.
코루틴이라고 해서 마법처럼 concurrent 하게 동작할 것 같지만 기본적으로는 순차적으로 실행되기 때문에 2초 이상의 수행시간이 소요됩니다.
async 키워드를 활용하여 concurrent하게 만들기
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 1022 ms
단순하게 async 키워드를 붙여주면 이제 1초 정도의 시간만 소요됩니다.
이때 두 함수간에는 의존성이 없어야 합니다.
launch와 async는 유사하지만 luanch는 job을 반환하고 async는 Deffered를 반환합니다.
await() 메서드를 통해 최종 결과를 가져올 때까지 기다릴 수 있으며 Deffered는 job을 상속받고 있지만 비동기에 대한 결과를 나중에 제공하겠다고 약속하는 가벼운 non-blokcing입니다.
Lazy 한 async 만들기
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
delay(1000L)
two.start() // start the second one
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
시작 매개변수를 CoroutineStart.LAZY로 설정하여 비동기를 지연 상태로 만들고, start나 await에 의해 요구될 때 코루틴이 시작됩니다.
one과 two 사이에 1초 동안 delay를 주면 기존에 1초가 소요되던 완료시간이 2초로 증가하게 됩니다.
GlobalScope를 활용하여 어디서나 호출할 수 있도록 사용하지 말기
// The result type of somethingUsefulOneAsync is Deferred<Int>
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
// The result type of somethingUsefulTwoAsync is Deferred<Int>
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
코루틴에서는 이런 스타일을 강력하게 권장하지 않습니다.
suspend를 붙이지 않고 어디서나 해당 함수를 호출할 수 있게 만들어낼 수 있습니다.
하지만 GlobalScope이기 때문에 예외가 발생하더라도 백그라운드에서 코루틴이 여전히 수행될 수 있습니다.
Structured concurrecy 활용하기
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
try {
failedConcurrentSum()
} catch(e: ArithmeticException) {
println("Computation failed with ArithmeticException")
}
}
suspend fun failedConcurrentSum(): Int = coroutineScope {
val one = async<Int> {
try {
delay(Long.MAX_VALUE) // Emulates very long computation
42
} finally {
println("First child was cancelled")
}
}
val two = async<Int> {
println("Second child throws an exception")
throw ArithmeticException()
}
one.await() + two.await()
}
//결과
Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException
GlobalScope가 아닌 coroutineScope를 활용합니다.
이렇게 되면 two를 반환하는 async 코루틴에서 예외가 발생하더라도 예외가 전파되어 다른 코루틴이 종료됩니다.