ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 공식문서로 알아보는 Composing suspending functions
    Kotlin/코루틴 2023. 12. 9. 00:01

    개요

    Kotlin 공식문서를 보며 코루틴의 suspend function 작성방법에 대해 알아보면서 실습해보고자 합니다.

     

     

    기본적으로 순차적으로 실행된다

    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 2017 ms

     

    suspend 키워드를 활용하여 2가지 메서드를 만들어 보고 1초의 delay를 주었습니다.

    순차적으로 호출되면서 13 + 29의 결과인 42를 만들어냅니다.

    코루틴이라고 해서 마법처럼 concurrent 하게 동작할 것 같지만 기본적으로는 순차적으로 실행되기 때문에 2초 이상의 수행시간이 소요됩니다.

     

    async 키워드를 활용하여 concurrent하게 만들기

    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 1022 ms

    단순하게 async 키워드를 붙여주면 이제 1초 정도의 시간만 소요됩니다.

    이때 두 함수간에는 의존성이 없어야 합니다.

     

    launch와 async는 유사하지만 luanch는 job을 반환하고 async는 Deffered를 반환합니다.

    await() 메서드를 통해 최종 결과를 가져올 때까지 기다릴 수 있으며 Deffered는 job을 상속받고 있지만 비동기에 대한 결과를 나중에 제공하겠다고 약속하는 가벼운 non-blokcing입니다.

     

    Lazy 한 async 만들기

    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
        delay(1000L)
        two.start() // start the second one
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")

    시작 매개변수를 CoroutineStart.LAZY로 설정하여 비동기를 지연 상태로 만들고, start나 await에 의해 요구될 때 코루틴이 시작됩니다.

    one과 two 사이에 1초 동안 delay를 주면 기존에 1초가 소요되던 완료시간이 2초로 증가하게 됩니다.

     

    GlobalScope를 활용하여 어디서나 호출할 수 있도록 사용하지 말기

    // The result type of somethingUsefulOneAsync is Deferred<Int>
    @OptIn(DelicateCoroutinesApi::class)
    fun somethingUsefulOneAsync() = GlobalScope.async {
        doSomethingUsefulOne()
    }
    
    // The result type of somethingUsefulTwoAsync is Deferred<Int>
    @OptIn(DelicateCoroutinesApi::class)
    fun somethingUsefulTwoAsync() = GlobalScope.async {
        doSomethingUsefulTwo()
    }

    코루틴에서는 이런 스타일을 강력하게 권장하지 않습니다.

    suspend를 붙이지 않고 어디서나 해당 함수를 호출할 수 있게 만들어낼 수 있습니다.

     

    하지만 GlobalScope이기 때문에 예외가 발생하더라도 백그라운드에서 코루틴이 여전히 수행될 수 있습니다.

     

    Structured concurrecy 활용하기

    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()
    }
    
    //결과
    Second child throws an exception
    First child was cancelled
    Computation failed with ArithmeticException

     

    GlobalScope가 아닌 coroutineScope를 활용합니다.

    이렇게 되면 two를 반환하는 async 코루틴에서 예외가 발생하더라도 예외가 전파되어 다른 코루틴이 종료됩니다.

    댓글

Designed by Tistory.