Grusie 안드로이드 개발 기술 블로그

[Android] 코루틴 async await 에 대한 고찰 비동기, 동기, 병렬처리 본문

안드로이드 개발/코틀린

[Android] 코루틴 async await 에 대한 고찰 비동기, 동기, 병렬처리

grusie 2024. 3. 29. 09:04
728x90
반응형
SMALL

회사 프로젝트를 개편하려던 중 화면을 불러오는 것이 너무나도 느리기에, 코드를 들여다 보니, 전부 다 동기처리로 되어있었다. 그냥 콜백을 보내고, 다음 함수를 호출하고의 문제가 아니라, 아예 콜백을 보내고? 콜백을 받아와서, 성공했을 때 그 다음 콜백을 요청하는 형태로 구현이 되어있어서.. 경악을 금치 못했다.

또한 OkHttp로 구현이 되어있었기에 Retrofit으로 변경하며, 클린아키텍처로 변경하면서 비동기, 병렬 처리에 대해 고민을 하였다.

 

예전에 suspend 함수의 통신 결과를 받아오기 위해 await()함수를 사용하였던 적이 있다.
그 땐 await()가 값을 가져오기 위해 쓰는 것이기에 동기라고만 생각 하고 넘어 갔었다.

 

여러 개의 서버통신을 비동기로 변경하면서 생긴 고민

  • "비동기로 처리하면, 서버통신이 전부 다 끝난 걸 어떻게 알지?"
  • "앞에 있는 함수가 서버통신 요청을 할 때 오래걸리면 그 다음 통신들은 계속 기다려야하네?"

라는 두 가지 의문점이 들었다.

 

그래서, 실행하는 것을 병렬로 처리하고, 마지막 통신이 끝났는지 판단하면 되겠다는 생각을 하게 되었다.

코틀린 환경에서 병렬처리를 한다는 것은 어떤 방법이 있을까?

1. RxJava/RxKotlin 의 zip 처리

2. 코루틴의 async를 활용한 deferred 관리

 

여러가지 방법이 있을 수 있으나, 떠올린 방법은 이렇게 두 가지였고 그 중 플로우를 사용하여 구현하게 되었다.

 

예를 들어 확인해 보자.

    fun test(){
        viewModelScope.launch {
            funA()
            funB()
        }
    }

    private suspend fun funA() {
        Logger.d("funA 서버통신 요청")
        delay(3000)

        Logger.d("funA 서버통신 응답")
    }

    private suspend fun funB() {
        Logger.d("funB 서버통신")
        delay(1000)
        Logger.d("funA 서버통신 응답")
    }

이런 코드가 있을 때 우리가 원하는 과정은, A와 B통신을 요청하고, 각각 비동기로 응답을 온 것을 사용하고 싶은 것이다.

하지만 코드를 이렇게 구현하게 되면, 

 

async await 결과

이런식으로, funA 서버통신이 요청 되고, 결과가 돌아온 뒤에야 funB를 호출한다.

 

병렬로 처리를 하기 위해서는, 실행할 코드를 async블럭으로 감싸면 된다.

fun test(){
    viewModelScope.launch {
        val funADeferred = async { funA() }
        val funBDeferred = async { funB() }

        funADeferred.await()
        funBDeferred.await()
    }
}

await()는 결과를 기다리는 것이기에, 두 deferred 변수가 다 처리가 된다면 다음 코드로 넘어간다.

 

그렇기에 만약, 동기 처리를 하고 싶다면 이런식으로 처리 할 수 있을 것 같다.

fun test(){
    viewModelScope.launch {
        val funADeferred = async { funA() }
        val funBDeferred = async { funB() }
        val funCDeferred = async { funC(funADeferred.await()) }

        funBDeferred.await()
        funCDeferred.await()
    }
}

private suspend fun funA(): String {
    Logger.d("funA 서버통신 요청")
    delay(3000)
    Logger.d("funA 서버통신 응답")
    return "A"
}

private suspend fun funB() {
    Logger.d("funB 서버통신 요청")
    delay(1000)
    Logger.d("funB 서버통신 응답")
}

private suspend fun funC(a: String) {
    Logger.d("$a 가 필요한 funC 서버통신 요청")
    delay(1000)
    Logger.d("funC 서버통신 응답")
}

funC는 funA의 결과값을 받아야 처리 되는 함수 이기에, A가 다 처리가 된 뒤에 funC가 동작을 해야하고, funB는 그것에 상관 없이 병렬로 처리 되어야 한다.

funC의 파라미터로 funADeferred의 await()를 보내 동기 처리를 한다.

결과

Async Await 처리 결과

의도한 대로 잘 나오는 것을 볼 수 있다.

 

많은 함수들을 병렬로 진행하려면, 개별로 변수를 만들지 않고, list 등을 사용하여 모아둔 후, awaitAll함수를 사용하면 된다.

val deferredList = listOf(
    async { funA() },
    async { funB() },
    async { funC() }
)

deferredList.awaitAll()

 

 

후기

함수를 병렬로 처리하는 것을 몰랐다는 것 부터 부끄러움이 밀려왔다.

그 동안 서버통신을 요청하는 부분 자체에서 오래걸리는 함수가 없었기에 그랬던 것 같다..

이번 기회에 속도를 많이 개선하였으니, 프로젝트를 다 완수한 뒤, 기존과 비교하면서 속도차이가 얼마나 나는지 확인해 볼 수 있을 것 같다.

반응형
LIST