출처 : kotlinlang.org/docs/reference/coroutines/composing-suspending-functions.html
오늘도 열심히 코루틴에 대해 공부해 보겠습니다.!!
Sequential by default
다음과 같은 간단한 suspend function 이 있습니다.
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
}
위 두함수를 연달아 호출 하면 어떻게 될까요?
fun main(){
runBlocking {
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
}
}
이렇게 호출을 하면
The answer is 42
Completed in 2008 ms
위의 결과를 확인 할 수 있습니다.
이를 통하여 코루틴 코드도, 일반적인 코드와 마찬가지로 순차적으로 동작하는 것을 알 수 있습니다.
Concurrent using async
만일 위의 두 suspend 함수에 종송성이 없고, 또 동시에 실행하여 더 따른 수행결과를 얻고 싶다면 어떻게 해야할까요?
async를 사용하면 됩니다. 개념적으로 async는 launch와 같습니다. 어떤 개념이냐면 모든 다른 코루틴과 동시에 동작할 수 있는 경략 thread인 분리된 코루틴을 시작합니다. 하지만 다른 점은 launch는 다른 결과 value를 반환하지 않고 job을 반환 합니다. 반면 async는 Deferred를 반환합니다. 그래서 .await()를 사용하여 지연된 결과를 받을 수 있고, Defferd 역시 job이기 때문에 취소 할 수 있습니다.
fun main(){
runBlocking {
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 1012 ms
이렇게 아까보다 더 빠른 결과를 확인 할 수 있습니다. 왜냐하면 동시에 동작하기 때문입니다. 즉, 코루틴에서 동시성은 명시적 입니다.!!! 항상!!
Lazily started async
async는 시작 매개 변수를 CoroutineStart.LAZY로 설정하여 지연시킬 수 있습니다. 이 모드에서는 await가 결과를 요청 할때 코루틴을 시작합니다. (또는 job의 시작 함수를 호출 할때)
fun main(){
runBlocking {
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")
}
}
이 역시 위의 async와 비슷한 결과를 보이지만 start를 호출 했기 때문에 두 async가 수행되었고, 그 결과를 .await로 가져와서 사용 합니다.
만일 start전에 await를 호출하게 될 경우 await가 코루틴을 실행하고 종료를 기다리기 때문에 동시에 수행되지 않고, 순차적으로 실행 됩니다. 이런경우에는 lazy의 사용성에 맞지 않게 됩니다.
println("The answer is ${one.await() + two.await()}")
one.start() // start the first one
two.start() // start the second one
Async-style functions
GlobalScope 참조가있는 비동기 코루틴 빌더를 사용하여 doSomethingUsefulOne 및 doSomethingUsefulTwo를 비동기 적으로 호출하는 비동기 스타일 함수를 정의 할 수 있습니다. 그리고 이러한 함수의 이름의 끝에 "… Async" 붙이므로서 비동기 계산만 시작하고 결과를 얻기 위해 deffered된 결과 값을 사용해야한다는 사실을 강조합니다.
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
// The result type of somethingUsefulOneAsync is Deferred<Int>
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
// The result type of somethingUsefulTwoAsync is Deferred<Int>
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
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")
}
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
}
Structured concurrency with async
async 코루틴 빌더는 CorountinScope의 확장으로 정의 되었기 때문에 , async를 scope에 너을 필요가 있고, 이것은 coroutineScope 함수가 제공하는 것입니다.
fun main() = runBlocking {
val time = measureTimeMillis {
println("The answer is ${concurrentSum()}")
}
println("Completed in $time ms")
}
suspend fun concurrentSum(): Int = coroutineScope {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
one.await() + two.await()
}
이렇게하면 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) // 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()
}
자식 중 하나가 실패하면 첫 번째 비동기 및 대기중인 부모가 어떻게 취소되는지 확인합니다.
'2023년 이전 > kotlin' 카테고리의 다른 글
코루틴(Coroutine) - Cancellation and Timeout (0) | 2020.09.13 |
---|---|
코루틴(Coroutine)이란 -2 (0) | 2020.09.13 |
코루틴(Coroutine)이란? -1 (0) | 2020.09.12 |
kotlin - 널이 될수 있는 타입(?), 안전 호출 연산자(?.), 엘비스 연산자(?:), 안전 캐스트(as?) (0) | 2020.03.18 |
kotlin - 수신 객체 지정 람다 (0) | 2020.03.10 |