본문 바로가기
2023년 이전/kotlin

코루틴(Coroutine) - Composing Suspending Functions

by JeongUPark 2020. 9. 20.
반응형

출처 : 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()
}

자식 중 하나가 실패하면 첫 번째 비동기 및 대기중인 부모가 어떻게 취소되는지 확인합니다.

반응형