기본적으로 순차 실행
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // 유용한 작업을 하는 것처럼 보이게 하기 위한 지연
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // 유용한 작업을 하는 것처럼 보이게 하기 위한 지연
return 29
}
Kotlin
복사
•
첫 번째 함수의 결과에 따라 두 번째 함수를 호출할지 결정해야 한다면 이 함수들을 순차적으로 실행하는 것이 필요하다.
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("결과는 ${one + two}")
}
println("완료 시간: $time ms")
// 출력:
// 결과는 42
// 완료 시간: 2017 ms
Kotlin
복사
•
코루틴 내에서 코드는 기본적으로 순차적으로 실행되기 때문에 위과 같이 일반적인 순차 호출을 사용할 수 있다.
async를 사용한 동시 실행
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("결과는 ${one.await() + two.await()}")
}
println("완료 시간: $time ms")
// 출력:
// 결과는 42
// 완료 시간: 1017 ms
Kotlin
복사
•
doSomethingUsefulOne 과 doSomethingUsefulTwo 함수 간에 의존성이 없다면 두 작업을 동시 실행하여 더 빠르게 결과를 얻을 수 있다.
•
async는 launch와 유사하게 별도의 코루틴을 시작한다.
•
하지만 launch는 결과 값을 반환하지 않는 Job을 반환하는 반면 async는 나중에 결과를 제공하는 Deferred를 반환한다.
•
Deferred는 결과를 기다릴 때 await()를 호출하여 사용할 수 있으며 필요 시 Deferred를 취소할 수도 있다.
async의 지연 실행
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
one.start() // 첫 번째 코루틴 시작
two.start() // 두 번째 코루틴 시작
println("결과는 ${one.await() + two.await()}")
}
println("완료 시간: $time ms")
// 출력:
// 결과는 42
// 완료 시간: 1017 ms
Kotlin
복사
•
async는 start 매개변수를 CoroutineStart.LAZY로 설정하면 지연 실행할 수 있다.
•
이 경우 await()가 호출될 때나 명시적으로 start() 함수가 호출될 때 코루틴이 시작된다.
•
두 코루틴을 정의했지만 실행은 프로그래머가 언제 시작할지 직접 제어할 수 있다.
•
start()를 호출하지 않고 await()만 호출하면 순차적인 동작을 유발하게 된다.
•
async(start = CoroutineStart.LAZY) 는 값의 계산에 서스펜딩 함수가 포함된 경우 표준 lazy 함수를 대체한다.
async 스타일 함수
// somethingUsefulOneAsync의 반환 타입은 Deferred<Int>입니다.
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
// somethingUsefulTwoAsync의 반환 타입은 Deferred<Int>입니다.
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
Kotlin
복사
•
GlobalScope를 사용한 async 스타일 함수 정의는 다른 프로그래밍 언어에서는 흔할 수 있지만 코틀린의 코루틴에서는 권장되지 않는다.
•
GlobalScope는 섬세한 API로 이를 사용하려면 명시적으로 @OptIn(DelicateCoroutinesApi::class)를 사용하여 동의해야 한다.
•
이 함수들은 서스펜딩 함수가 아니므로 어디서든 사용할 수 있지만 이 함수들의 사용은 항상 호출 코드와 비동기 실행된다는 의미를 내포한다.
// 이 예제에서는 main 함수 오른쪽에 runBlocking을 사용하지 않습니다.
fun main() {
val time = measureTimeMillis {
// 코루틴 외부에서 비동기 작업을 시작할 수 있습니다.
val one = somethingUsefulOneAsync()
val two = somethingUsefulTwoAsync()
// 그러나 결과를 기다리기 위해서는 서스펜딩 함수 또는 차단 함수가 필요합니다.
// 여기서는 runBlocking { ... }을 사용하여 메인 스레드를 차단하고 결과를 기다립니다.
runBlocking {
println("결과는 ${one.await() + two.await()}")
}
}
println("완료 시간: $time ms")
}
Kotlin
복사
•
만약 one 변수 선언과 one.await() 사이에서 오류가 발생해 프로그램이 예외를 던지고 실행이 중단된다면 어떻게 될까?
•
이 경우 프로그램이 중단되었음에도 불구하고 somethingUsefuleOneAsync는 백그라운드에서 계속 실행되고 있을 것이다.
•
이러한 문제는 구조적 동시성을 사용할 때는 발생하지 않는다.
구조적 동시성과 async 사용
suspend fun concurrentSum(): Int = coroutineScope {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
one.await() + two.await()
}
Kotlin
복사
•
async 코루틴 빌더는 CoroutineScope의 확장 함수로 정의되어 있기 때문에 이 범위 내에서 실행되어야 하며 이를 위해 coroutineScope 함수를 사용한다.
val time = measureTimeMillis {
println("The answer is ${concurrentSum()}")
}
println("Completed in $time ms")
// 출력:
// The answer is 42
// Completed in 1017 ms
Kotlin
복사
•
이 방식으로 concurrentSum 함수 내부에서 오류가 발생해 예외를 던질 경우 해당 범위 내에서 실행된 모든 코루틴이 취소된다.
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) // 매우 긴 연산을 모방
42
} finally {
println("첫 번째 자식이 취소되었습니다")
}
}
val two = async<Int> {
println("두 번째 자식이 예외를 던집니다")
throw ArithmeticException()
}
one.await() + two.await()
}
// 출력:
// 두 번째 자식이 예외를 던집니다
// 첫 번째 자식이 취소되었습니다
// Computation failed with ArithmeticException
Kotlin
복사
•
취소는 항상 코루틴 계층을 통해 전파된다.
•
async의 첫 번째 자식과 그를 기다리던 부모가 두 번째 자식의 실패로 인해 모두 취소된다.