오늘도 더 나은 코드를 작성하였습니까?

Kotlin 코루틴으로 앱 성능 향상 본문

Coroutine

Kotlin 코루틴으로 앱 성능 향상

hik14 2021. 4. 7. 15:33

장기 실행 작업 관리

 

코루틴은 장기 실행 작업을 처리하는 두 작업을 추가하여 일반 함수를 기반으로 빌드됩니다. 

invoke(또는 call) 및 return 외에도 코루틴은 suspend  resume을 추가합니다.

  • suspend는 현재 코루틴 실행을 일시중지하고 모든 로컬 변수를 저장합니다.
  • resume은 정지된 위치부터 정지된 코루틴을 계속 실행합니다.

suspend 함수는 다른 suspend 함수에서 호출하거나 코루틴 빌더(예: launch)를 사용하여 새 코루틴을 시작하는 방법으로만 호출가능하다.

 

suspend fun fetchDocs() {                             // Dispatchers.Main
    val result = get("https://developer.android.com") // Dispatchers.IO for `get`
    show(result)                                      // Dispatchers.Main
}

suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }

 

get()은 여전히 기본 스레드에서 호출되지만 네트워크 요청을 시작하기 전에 코루틴을 정지한다.

네트워크 요청이 완료되면 get은 콜백을 사용하여 기본 스레드에 알리는 대신 정지된 코루틴을 재개합니다

 

Kotlin은 스택 프레임을 사용하여 로컬 변수와 함께 실행 중인 함수를 관리합니다.

코루틴을 정지하면 현재 스택 프레임이 복사되고 저장됩니다.

재개되면 스택 프레임이 저장된 위치에서 다시 복사되고 함수가 다시 실행됩니다.

코드가 일반적인 순차 차단 요청처럼 보일 수도 있지만 코루틴은 네트워크 요청이 기본 스레드를 차단하지 않도록 합니다.

 

기본 안전을 위해 코루틴 사용

 

Kotlin 코루틴은 디스패처를 사용하여 코루틴 실행에 사용되는 스레드를 반드시 확인

코드를 기본 스레드 외부에서 실행하려면 기본 또는 IO 디스패처에서 작업을 실행하도록 Kotlin 코루틴에 지시하면 됩니다.

Kotlin에서 모든 코루틴은 기본 스레드에서 실행 중인 경우에도 디스패처에서 실행되게 해야한다. 

(코루틴은 자체적으로 정지될 수 있으며 디스패처는 코루틴 재개를  담당한다. suspend)

 

Dispatchers.Main  

- main 디스패처를 사용하여 기본 Android 메인 스레드에서 코루틴을 실행한다.

- main 디스패처는 UI와 상호작용하고 빠른 작업을 실행하기 위해서만 사용한다.

예를 들어 suspend 함수를 호출하고 Android UI 프레임워크 작업을 실행하며 LiveData 객체를 업데이트합니다.

 

Dispatchers.IO

- IO 디스패처는 기본 스레드 외부에서 디스크 또는 네트워크 I/O를 실행하도록 최적화되어 있습니다.

예를 들어 ROOM 구성요소를 사용하고 파일에서 읽거나 파일에 쓰며 네트워크 작업을 실행합니다.

 

Dispatchers.Default

-  디스패처는 CPU를 많이 사용하는 작업을 기본 스레드 외부에서 실행하도록 최적화되어 있습니다.

예를 들어 대용량의 목록을 정렬하고 JSON을 파싱합니다

 

get의 본문 내에서 withContext(Dispatchers.IO)를 호출하여 IO 스레드 풀에서 실행되는 블록을 생성한다.

블록 안에 넣은 코드는 항상 IO 디스패처를 통해 실행됩니다. 밖은 Main 스레드에서 실행된다. 

withContext는 그 자체로 suspend 함수이고  get 함수도 suspend 함수입니다.

suspend fun fetchDocs() {                      // Dispatchers.Main
    val result = get("developer.android.com")  // Dispatchers.Main
    show(result)                               // Dispatchers.Main
}

suspend fun get(url: String) {                 // Dispatchers.Main
    withContext(Dispatchers.IO) {              // Dispatchers.IO (main-safety block)
        /* perform network IO here */          // Dispatchers.IO (main-safety block)
    }                                          // Dispatchers.Main
}

코루틴을 사용하면 로직을 세분화된 스레드에 실행되도록 할 수있다. 

 

withContext()를 사용하면 콜백을 도입하지 않고도 몇줄의 코드로도 스레드 풀을 제어할 수 있다,

데이터베이스에서 읽기, 네트워크 요청 실행과 같은 매우 작은 함수에 이를 적용할 수 있습니다. 

 

withContext()를 사용하여 모든 함수가 기본적으로 안전한지, (즉 기본 스레드에서 함수를 호출할 수 있는지 확인하는 것)

이 경우 함수를 호출하는 부분에서는 함수를 실행하는 데 사용해야 할 스레드를 생각할 필요가 없습니다

 

 예에서 fetchDocs()는 기본 스레드에서 실행되지만 이 함수는 백그라운드에서 네트워크 요청을 실행하는 get을 안전하게 호출 가능하다.

코루틴은 suspend  resume을 지원하므로 withContext 블록이 차단되는 즉시 기본 스레드의 코루틴이 get 결과와 함께 재개됩니다.

 

 

* suspend를 사용해도 백그라운드 스레드에서 함수를 실행하도록 Kotlin에 지시하지 하는것이 아니다.

(정지 및 재개가 가능한 함수임을 알려주는것일 뿐)

일반적으로 suspend 함수는 main 스레드에서 작동합니다. 또한 기본 스레드에서 코루틴을 실행하는 것이 일반적입니다.

디스크에서 읽기 또는 디스크에 쓰기, 네트워크 작업 실행, CPU 집약적인 작업 실행 등과 같은 기본적인 안전이 요구되는 withContext()는 항상 suspend 함수 내에서 사용해야 합니다.

 

withContext()의 성능

withContext()는 상응하는 콜백 기반 구현에 비해 오버헤드가 추가되지 않습니다.(즉 스레드가 교체되는 비용이 적다는 소리다.)

또한 일부 상황에서 상응하는 콜백 기반 구현을 능가하도록 withContext() 호출을 최적화할 수 있습니다.

 

예를 들어, 함수가 네트워크를 10회 호출하는 경우 외부 withContext()를 사용하여 스레드를 한 번만 전환하도록 Kotlin에 지시할 수 있습니다. 그러면 네트워크 라이브러리에서 withContext()를 여러 번 사용하더라도 동일한 디스패처에 유지되고 스레드가 전환되지 않습니다.

또한 Kotlin은 가능한 한 스레드 전환을 방지하도록 Dispatchers.Default와 Dispatchers.IO 간의 전환을 최적화합니다.

 

중요: 스레드 풀을 사용하는 디스패처(예: Dispatchers.IO 또는 Dispatchers.Default)를 사용해도 블록이 처음부터 끝까지 동일한 스레드에서 실행된다는 보장은 없습니다. 경우에 따라 Kotlin 코루틴이 다른 스레드의 실행을 suspend  resume 이후로 이동할 수 있습니다.

즉,  withContext() 블록에 동일한 스레드를 가리키지 않을 수 있습니다.

 

 

'Coroutine' 카테고리의 다른 글

Android의 코루틴 권장사항  (1) 2021.04.07
코루틴 시작  (0) 2021.04.07
Android의 Kotlin Coroutine  (0) 2021.04.07