Search

Composing suspending functions

기본적으로 순차 실행

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의 첫 번째 자식과 그를 기다리던 부모가 두 번째 자식의 실패로 인해 모두 취소된다.