Shunz Android Dev Note

코틀린 코루틴의 비동기 프로그래밍 - async, await, awaitAll 본문

Kotlin

코틀린 코루틴의 비동기 프로그래밍 - async, await, awaitAll

_Shun_ 2023. 11. 30. 08:07

 

비동기 프로그래밍은 애플리케이션을 개발할 때 Main Thread를 블럭킹하지 않고 시간이 오래 걸리는 작업 (예 : 원격 저장소에서 데이터를 가져오기, I/O 작업) 등 여러 작업을 동시에 수행할 수 있도록 해주는 중요한 테크닉입니다. 코틀린은 Android App 및 Backend 개발을 위해 강력한 언어로서 kotlin coroutine을 통해 비동기 프로그래밍을 탁월하게 지원합니다. 이번 포스팅에서는 비동기 프로그래밍을 쉽게 만드는 kotlin coroutine의 세 가지 필수 기능인 async{}, await, awaitAll의 개념에 대해 알아보겠습니다.

 

 

빌더 함수 async {}

kotlin coroutine에서 async{}는 새로운 코루틴을 시작하는 코루틴 빌더로, 비동기 연산을 시작한다는것을 의미합니다. 이 함수는 미래에 언젠가는 사용할 수 있을 결과를 줄 수 있는 Deferred 인스턴스를 반환합니다. async{}를 사용하면 여러 비동기 작업을 동시에 수행할 수 있어 코드의 전반적인 효율성이 향상됩니다.

 

Deferred는 비동기 연산이 완료된 후 나중에 사용할 수 있는 결과를 주겠다는 약속을 의미하는 일반적인 인터페이스입니다. async{} 코루틴 빌더에서 반환되면, 연산이 완료되자마자 결과값을 가져오거나 예외를 처리하는 데 사용할 수 있습니다.

// 라이브러리 코드 일부 발췌
public interface Job : CoroutineContext.Element {
    @ExperimentalCoroutinesApi
    public val parent: Job?
    public val isActive: Boolean
    public val isCompleted: Boolean
    public val isCancelled: Boolean   
    ...
}

public interface Deferred<out T> : Job {
    public suspend fun await(): T
    
    @ExperimentalCoroutinesApi
    public fun getCompleted(): T
    
    @ExperimentalCoroutinesApi
    public fun getCompletionExceptionOrNull(): Throwable?
 }

 

Deferred 인터페이스는 결과값의 타입을 나타내는 제네릭 타입 T를 정의하고 있고 Job 인터페이스를 확장하고 있습니다. 인터페이스에는 코루틴 함수의 진행 상황을 제공해주는 여러 메서드와 job의 상태를 제공하고 있습니다.

 

동시 처리(concurrent processing)를 위한 await 사용

async{}를 통해 단일 코루틴을 시작한 경우 await() 함수를 사용하여 완료를 기다렸다가 결과를 가져올 수 있습니다. await() 함수는 지연된 완료를 기다렸다가 결과를 반환하는 suspending function입니다.

fun main() = runBlocking {

    // async 빌더 함수를 통하여 비동기 완료 작업을 시작.
    val deferredResult: Deferred<String> = async {
        fetchAsyncData("Hello World!")
    }

    // 다른 작업을 여기에서 concurrent하게 진행...

    // 이제 await 함수를 사용하여 비동기 연산이 완료될때까지 기다리고, 결과를 전달 받는다.
    val result = deferredResult.await()
    println("결과 : $result")
}

suspend fun fetchAsyncData(reqMsg: String): String {
    delay(1000) 
    return reqMsg + " 무거운 작업 완료"
}
결과 : Hello World! 무거운 작업 완료

 

 

동시 처리(concurrent processing)을 위한 awaitAll 사용

async{}를 통해 여러 개의 코루틴이 실행된 경우에는 결과 처리를 진행하기 전에 모든 코루틴이 완료될 때까지 기다려야 할 수도 있습니다. 이 작업을 awaitAll() 함수를 사용하면 쉽게 처리 할 수 있습니다. 이 함수를 사용하면 주어진 모든 Deferred 인스턴스가 완료될 때까지 기다렸다가  입력과 같은 순서로 결과 목록을 반환하는 suspending function입니다.

fun main() = runBlocking {

    // async 빌더 함수를 통하여 비동기 완료 작업을 시작.
    val deferredResults = listOf(
        async { fetchAsyncData("Hello World!") },
        async { fetchAsyncData("Hello Friend!") },
        async { fetchAsyncData("Hello Dude!") }
    )

    // 다른 작업을 여기에서 concurrent하게 진행...

    // 이제 awaitAll 함수를 사용하여 비동기 연산이 완료될때까지 기다리고, 결과를 전달 받는다.
    val results = awaitAll(*deferredResults.toTypedArray())
    results.forEachIndexed { index, result ->
        println("${index} 결과 : $result")
    }
}

suspend fun fetchAsyncData(reqMsg: String): String {
    delay(1000) 
    return reqMsg + " 무거운 작업 완료"
}
0 결과 : Hello World! 무거운 작업 완료
1 결과 : Hello Friend! 무거운 작업 완료
2 결과 : Hello Dude! 무거운 작업 완료

 

 

결론

코틀린 코루틴은 코틀린 응용 프로그램에서 비동기식 프로그래밍을 처리할 수 있는 강력하고 간결한 방법을 제공합니다. 코루틴 빌더 async {} 를 활용하면 여러 비동기식 연산을 동시에 시작하고 wait를 사용하여 결과를 가져올 수 있습니다. awaitAll() 함수와 결합하면 이 모든 작업이 효율적으로 완료될 때까지 기다릴 수 있습니다. 이 방법을 사용하면 코드 가독성이 더 좋아지고 응용 프로그램의 전반적인 성능도 향상시킬 수 있습니다.

 

비동기 프로그래밍은 예외적인 케이스를 처리하고 메모리 릭을 방지하기 위해 적절한 오류 처리와 취소가 중요하다는것도 기억해야 합니다. 코틀린 코루틴은 이러한 측면에서 효과적으로 관리하기 위한 다양한 방법을 제공합니다. 다음 포스팅에서는 다양한 코루틴에서의 예외처리에 대하여 살펴보도록 하겠습니다.

 


 

감사합니다!