코르크의 기본 사용 방법(병렬 처리/병렬 처리)

의 목적


회사 업무 내에는 HTTP 통신을 통해 API 요청을 수행하는 구현 작업이 있습니다.
그때는 코틀린의 코르크를 사용해 비동기 처리가 이뤄졌지만, 시간이 없어 코르크의 기본적인 사용법을 전혀 이해하지 못한 채 실장했다.
그 후 연골소의 기초를 잘 배우고 싶을 때 다음과 같은 기사를 만났다.
Coroutines on Android (part I): Getting the background
죄송하지만 이 기사를 통해 제 이해가 정말 대단했던 점을 반성하고 샘플 프로젝트를 만들어 공부했습니다.
다음은 자습용 견본 프로젝트를 통해 배운 코르크를 사용하는 API 요청을 병렬 처리와 병렬 처리를 통해 구체적인 방법을 요약한다.

앞으로 코르크를 배우고 싶은 분들께.


앞으로 코르크를 배우고 싶은 분들은 아래에 소개된 기사와 유튜브를 추천합니다.
  • Coroutines on Android (part I): Getting the background

  • 위에서 열거한 자료 덕분에 나는 코린틴의 기본 개념을 배웠다.

    나의 잘못된 해석


    우선 제 잘못된 해석을 말씀드리겠습니다.
    나는 이전에 다중 루틴으로 대응하는 언어를 개발한 적이 없기 때문에 다중 루틴을 사용하는 실행 처리에 대해서도 약간의 이해만 했다.
    내 설명은 이렇다.
  • CoroutineScope.launch로 둘러싸인 처리는 주 스레드가 아니라 백엔드 스레드에서 실행
  • suspend 수식자를 추가한 자작 함수에서 명령 사용WithContext 백엔드 라인에서 사용
  • 정확한 해석


    1. CoroutineScope.launch로 둘러싸인 처리는 주 라인이 아니라 백엔드 라인에서 실행됩니다


    위에서 열거한 보도는 아래와 같다.
    Coroutines will run on the main thread, and suspend does not mean background.
    코르크를 만들 때 명확하게 표시하지 않으면 기본적으로 주 라인에서 처리됩니다.
    fun example() {
        viewModelScope.launch(Dispatchers.IO) {
            // IOバックグラウンドスレッドで実行する処理
        }
    }
    

    2.suspend 수정자가 있는 자작 함수에서 명령은WithContext를 사용하여 백엔드 라인에서 사용


    코르크 내 처리에서 WithContext를 사용하여 처리의 라인을 바꿉니다.
    예를 들어 API 요청과 같은 비동기 처리는 IO 백엔드 스레드이고 계산량이 비교적 무거운 처리는Default 백엔드 스레드이다.
    참고 자료: CoroutineDispatcher
    또한 함수에 수정자suspend를 더하면 UI 작업의 주 스레드를 방해하지 않고 함수를 일시적으로 중단할 수 있습니다.
    그리고 WithContext와 조합하여 다른 라인에서 임시 중단 처리를 실행하여 비동기 처리를 실현한다.
    suspend fun example() {
        doTaskOnMainThread()           // メインスレッドで実行
        withContext(Dispatchers.IO) {  // IOバックグラウンドスレッドで実行
            doTaskOnBackGroundThread() // IOバックグラウンドスレッドで実行
        }                              // IOバックグラウンドスレッドで実行
        doTaskOnMainThread()           // メインスレッドで実行
    }
    

    샘플 항목


    이번에 준비한 샘플 프로젝트는 3가지 모델을 준비했다.
  • 병렬 처리
  • 병렬 처리(async/await)
  • 병렬 처리(launch/join)
  • 1. 병렬 처리


    3개의 API 요청이 병렬 처리 순서대로 실행되고 결과는 UI 화면에 표시됩니다.
    이번에는 실제 API 요청이 아닌 각 요청을 수행하는 데 1초의 지연이 발생했습니다.따라서 버튼을 3초 누르면 처리가 끝납니다.

    순서대로라면 먼저 뷰 모델의 라이프 사이클과 결합한 코르크 작용경viewModelScope을 제작한다.
    라이프 사이클과 결합해 만들어진 코린의 범위는 View 모델들이 폐기된 시점에 자동으로 취소되기 때문이다.
    Activity와 Fragment의 생명주기와 결합된 코르크 작용경lifeCycleScope이 있다.
    그리고 이 코린틴 범위 내의 처리는 순서대로 집행된다.
    MainVieweModel
    fun requestApi() {
        // 1. コルーチンスコープを作成し、中身の処理がメインスレッドで逐次実行されていく
        viewModelScope.launch {
            _isLoading.value = true  // 2. ローディング中状態にする
            fetchApi1()              // 3. API1 へリクエスト
            fetchApi2()              // 4. API2 へリクエスト
            fetchApi3()              // 5. API3 へリクエスト
            _isLoading.value = false // 6. ローディング完了状態にする
        }
    }
    
    다음은 3개의 API 요청을 수행하는 방법fetchApiX의 내용을 살펴보겠습니다.
    이 방법에서 WithContext을 통해 추천하는 IO 백엔드 스레드로 전환하고 1초의 요청을 실행하여 결과를 LiveData에 반영한다.
    MainVieweModel
    private suspend fun fetchApi1() {
        // 1. IOバックグラウンドスレッドに切り替える
        withContext(Dispatchers.IO) {
            delay(1_000)                      // 2. 1 秒間遅延させる(IOバックグラウンドスレッドで実行)
            _dataFromApi1.postValue("Kotlin") // 3. メインスレッドではないため、postValue で値を更新(IOバックグラウンドスレッドで実行)
        }
    }
    
    병렬 처리의 실행 처리 프로세스 이미지는 다음과 같다.

    2. 병렬 처리(async/await)


    다음은 async와 await를 사용하는 병행 처리를 살펴봅시다.
    병렬 처리는 API 요청을 순차적으로 수행하지만 병렬 처리에서는 모든 API 처리를 동시에 수행합니다.
    따라서 병렬 처리에서 모든 API 요청이 완료될 때까지 1초가 소요됩니다.

    MainViewModel
    fun requestApiWithAsyncAndAwait() {
        // 1. コルーチンスコープを作成し、中身の処理がメインスレッドで逐次実行されていく
        viewModelScope.launch {
            _isLoading.value = true     // 2. ローディング中状態にする
            val apiAsyncList = listOf(  // 3. async を使って新しいコルーチンを作成し、その中で各 API リクエストを実行する。
                async { fetchApi1() },  //    Deffered 型の返り値をリスト化する。
                async { fetchApi2() },
                async { fetchApi3() },
            )
            apiAsyncList.awaitAll()     // 4. 全ての API リクエストの実行完了を待つ
            _isLoading.value = false    // 5. ローディング完了状態にする
        }
    }
    
    병렬 처리의 실행 처리 프로세스 이미지는 다음과 같다.

    3. 병렬 처리(launch/join)


    launch/join을 사용하면 async/await와 같은 병렬 처리를 할 수 있습니다.viewModelScope의 코르크 작용 범위 내에서 launch를 사용하여 새로운 코르크 작용 영역을 만들고 그 중에서 API 요청을 한다.
    launch는 Job형의 되돌아오는 값을 되돌려줍니다. Job형 목록 apiJobsList 의 모든 요소가 완성될 때까지 기다립니다.
    MainViewModel
    fun requestApiWithLaunchAndJoin() {
        // 1. コルーチンスコープを作成し、中身の処理がメインスレッドで逐次実行されていく
        viewModelScope.launch {
            _isLoading.value = true     // 2. ローディング中状態にする
            val apiJobsList = listOf(   // 3. lanuch を使って新しいコルーチンを作成し、その中で各 API リクエストを実行する。
                launch { fetchApi1() }, //    Job 型の返り値をリスト化する
                launch { fetchApi2() },
                launch { fetchApi3() },
            )
            apiJobsList.joinAll()       // 4. 全ての API リクエストの実行完了を待つ
            _isLoading.value = false    // 5. ローディング完了状態にする
        }
    }
    

    컨디션


    라이브러리 작업


    dependencies {
        /**
         * ViewModel ライブラリ
         */
    
        def lifecycle_version = "2.4.0"
    
        // ViewModel
        implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
        // ViewModel utilities for Compose
        implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")
        // LiveData
        implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
        // Lifecycles only (without ViewModel or LiveData)
        implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version")
    
        /**
         * Kotlin Coroutine ライブラリ
         */
    
        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2")
    }
    

    개발 환경


    Kotlin version: 1.6.10
    

    샘플 코드


    https://github.com/Kotaro666-dev/androidDevelopment/tree/main/mockups/KotlinCoroutineSample

    참고 자료

  • Coroutine context and dispatchers
  • Coroutines on Android (part I): Getting the background

  • Kotlin 젤라틴을 통해 응용의 성능 개선
  • 좋은 웹페이지 즐겨찾기