일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 코루틴 공식문서
- Kotlin Serialization
- android orbit
- 코틀린 에러 핸들링
- runCatching
- Unsupported metadata version. Check that your Kotlin version is >= 1.0: java.lang.IllegalStateException
- RecyclerView Sticky Header
- 안드로이드 컴포즈
- hilt
- BOJ
- 백준
- power menu 라이브러리
- Coroutine
- android compose orbit
- 스레드 vs 코루틴
- Android Compose
- 안드로이드 무한 스크롤
- 백준 2615
- 힐트
- 안드로이드 커스텀 뷰
- Android Custom View
- Sticky Header RecyclerView
- 코루틴 공식 문서
- AAC ViewModel
- Android Compose Navigation
- power menu
- viewmodel
- 코루틴
- Thread vs Coroutine
- Hilt 에러
- Today
- Total
Beeeam
코루틴 공식 문서 읽기 (Composing suspending functions) 본문
https://kotlinlang.org/docs/composing-suspending-functions.html
Composing suspending functions | Kotlin
kotlinlang.org
이 섹션에서는 suspend 함수의 구성에 다양한 접근법들을 다룬다.
Sequential by default
코루틴의 코드는 일반적인 코드와 마찬가지로 순차적 호출을 사용한다.
fun main() = runBlocking<Unit> {
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 2010 ms
코루틴 내에서 순차적으로 함수를 호출하여 각 변수에 값을 할당한 다음에 결과 값을 출력하는 것을 볼 수 있다. 그리고 결과의 총 소요 시간을 보면 함수가 순차적으로 호출 되었기 때문에 각각의 함수의 소모 시간의 합이라는 것을 알 수 있다.
Concurrent using async
만약 앞의 예의 두 suspend 함수가 서로 의존성이 없고 둘 중 그냥 빨리 처리되는 것부터 값을 받아오고자 하면 async { … } 빌더를 사용하면 된다.
비동기적으로 실행하게 되면 위의 코드를 더 빠르게 처리할 수 있다.
async은 launch 처럼 새로운 코루틴을 생성하고, 실행하지만 launch는 job을 반환하고 결과 값을 전달하지 않지만, 비동기는 Deferred를 반환하는 차이점이 있다.
Deferred: job을 확장하는 인터페이스로 만들어져서 job의 모든 특성을 가지고 있다. async을 사용하고 await()을 사용하면 Deferred를 반환한다.
fun main() = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
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 1025 ms
먼저 눈에 띄는 점은 시간이 이전 예제보다 2배 감소 된 것이다. 이는 두 개의 코루틴이 동시에 실행 되기 때문이다.
이전 예제와 코드가 달라진 것은 doSomethingUseful…() 함수를 async { … } 빌더 내부에서 사용하는 것이다. 이 때문에 값을 반환 받을 때도 await() 함수를 사용하는 것을 볼 수 있다.
Lazily started async
async에 start 인자로 CoroutineStart.LAZY를 할당하여 시작을 지연할 수 있다. 이렇게 해서 지연된 함수는 await() 함수에서 결과가 필요하거나, 작업의 시작 함수가 호출된 경우에만 코루틴을 시작한다.
fun main() = runBlocking<Unit> {
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")
}
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 1042 ms
이전 예제와 출력 되는 결과는 다른 것이 없다. 하지만 실행되는 과정은 많이 다르다.
val 변수 = async( … } 에서 코루틴 함수가 바로 실행 되는 것이 아니라 값이 필요한 밑의 변수.start() 에서 시작이 된다.
만약 start 함수를 주석 처리 하고 실행 했다면 await 함수에서 두 함수가 실행이 됐을 것이고, 이로 인해 처음 예제에서 처럼 총 소요 시간은 각각의 함수의 소요 시간의 합이 된다.
Async-style functions
위의 예제의 doSomethingUseful… 함수를 비동기 스타일의 함수로 정의 할 수 있다. 이는 GlobalScope의 async { … } 빌더로 구현이 가능하다.
이 함수들은 비동기 연산들만 시작하고, …Async 접미사를 사용하여 결과를 얻기 위해서는 Deferred을 사용해야 함을 강조한다.
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
위와 같은 형식으로 비동기 스타일 함수를 선언할 수 있다. 그런데 GlobalScope는 non-trivial 방법으로 역효과가 날 수 있는 API라서 @OptIn(DelicateCoroutinesApi::class)와 함께 사용해야 한다.
이렇게 선언된 xxxAsync 함수는 suspend 함수가 아니라서 어디서나 사용이 가능하다. 하지만 이 함수를 실행하면 항상 비동기로 실행됨을 기억해야 한다.
fun main() {
val time = measureTimeMillis {
// we can initiate async actions outside of a coroutine
val one = somethingUsefulOneAsync()
val two = somethingUsefulTwoAsync()
// but waiting for a result must involve either suspending or blocking.
// here we use `runBlocking { ... }` to block the main thread while waiting for the result
runBlocking {
println("The answer is ${one.await() + two.await()}")
}
}
println("Completed in $time ms")
}
위의 코드는 비동기 스타일 함수를 코루틴이 아닌 곳에서 사용하는 예시이다.
근데 만약에 val one = somethingUsefulOneAsync() 와 one.await() 사이에서 오류가 있으면 실행 중 예외가 발생할 것 이다.
전역 에러 핸들러는 이 예외를 감지하고 개발자에게 예외를 보고 할 수 있다. 하지만 초기화 작업이 중단 되었음에도 불구하고 somethingUsefulOneAsync 가 백그라운드에서 여전히 실행 중인 문제가 생길 수 있다.
이러한 문제는 구조화된 동시성에서는 발생하지 않는다.
Structured concurrency with async
Concurrent using async 예제에서 doSomethingUseful… 함수들을 동시에 실행하고 해당 함수들의 반환 값을 합으로 반환하는 함수를 만들어보았다.
async { … } 빌더는 코루틴 스코프의 확장 함수로 정의되어 있기 때문에 이 빌더를 코루틴 스코프 안에서 호출이 가능할 수 있다. 이는 coroutineScope() 함수가 제공한다.
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()
}
main 함수에서 failedConcurrentSum() 함수를 실행하면 호출한 함수 내부의 await() 함수를 만나면서 두 개의 비동기 연산이 동시에 실행된다. 그런데 one에서는 지연 함수 때문에 값을 반환하지 못하고, two의 값이 먼저 출력된다. 그리고 예외를 발생하면서 one의 finally 문이 실행되어 문장을 출력하고, 그리고 이 예외가 main 함수에도 전달이 되서 catch 문 안의 코드가 실행되어 문장이 출력된다.
'Kotlin' 카테고리의 다른 글
코루틴 공식 문서 읽기 (Asynchronous Flow 2) (1) | 2023.04.13 |
---|---|
코루틴 공식 문서 읽기 (Asynchronous Flow 1) (0) | 2023.04.13 |
코루틴 공식 문서 읽기 (Coroutine context and dispatchers) (0) | 2023.03.28 |
코루틴 공식 문서 읽기 (Coroutine Cancellation and timeouts) (0) | 2023.03.05 |
코루틴 공식 문서 읽기 (Coroutines basics) (1) | 2023.03.03 |