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

코루틴 시작 본문

Coroutine

코루틴 시작

hik14 2021. 4. 7. 17:05

다음 두 가지 방법 중 하나로 코루틴을 시작할 수 있다.

 

launch

- 새 코루틴을 시작하고 호출자에게 결과를 반환하지 않습니다. '

- 실행 후 삭제로 간주되는 모든 작업은 launch를 사용하여 시작할 수 있습니다.

 

async

- 새 코루틴을 시작하고 await라는 suspend 함수로 결과를 반환하도록 허용합니다.

 

보통 일반 함수는 await를 호출할 수 없으므로 일반 함수에서 새 코루틴을 launch하고 그 안에서 async를 사용한다.

async는 다른 코루틴 내부에서만 사용하거나 suspend 함수 내에서 병렬 분해를 실행할 때 사용합니다

 

주의사항

launch async는 예외를 서로 다르게 처리합니다. 

async는 최종 await 호출을 예상하므로 예외를 보유하고 await 호출의 일부로 예외를 다시 발생시킵니다.

즉, await를 사용하여 일반 함수에서 새 코루틴을 시작하는 경우 예외를 자동으로 삭제할 수 있습니다.

이렇게 삭제된 예외는 비정상 종료 측정항목에 나타나지 않거나 logcat에 기록되지 않습니다. 자세한 내용은 코루틴의 취소 및 예외를 참조.

 

병렬 분해

suspend 함수에 의해 시작되는 모든 코루틴은 함수가 반환되면 중지되어야 하므로

반환 전에 그러한 코루틴이 완료되도록 보장해야 할 필요가 있다. 

 

Kotlin에서 구조화된 동시 실행을 사용하여 하나 이상의 코루틴을 시작하는 coroutineScope를 정의할 수 있습니다.

 

이후 함수 내부에서 await()(단일 코루틴의 경우) 또는 awaitAll()(여러 코루틴의 경우)을 사용하여 함수에서 반환하기 전에 이러한 코루틴이 완료되도록 보장할 수 있습니다

.

예를 들어 두 문서를 비동기식으로 가져오는 coroutineScope를 정의해보겠습니다. 각 지연된 참조에서 await()를 호출하여 값을 반환하기 전에 두 async 작업이 모두 완료되도록 보장합니다.

 

suspend fun fetchTwoDocs() =
    coroutineScope
{
       
val deferredOne = async { fetchDoc(1) }
       
val deferredTwo = async { fetchDoc(2) }
        deferredOne
.await()
        deferredTwo
.await()
   
}

 

아래 예제와 같이 컬렉션에서 awaitAll()을 사용할 수도 있습니다.

 

suspend fun fetchTwoDocs() =        // called on any Dispatcher (any thread, possibly Main)
    coroutineScope
{
       
val deferreds = listOf(     // fetch two docs at the same time
            async
{ fetchDoc(1) },  // async returns a result for the first doc
            async
{ fetchDoc(2) }   // async returns a result for the second doc
       
)
        deferreds
.awaitAll()        // use awaitAll to wait for both network requests
   
}

 

 

fetchTwoDocs()는 async를 통해 새 코루틴을 시작한다.  awaitAll()을 사용하여 실행된 코루틴이 완료되도록 기다렸다가 반환합니다.

하지만 awaitAll()을 호출하지 않았더라도 coroutineScope 빌더는 모든 새 코루틴이 완료될 때까지 fetchTwoDocs를 호출한 코루틴을 재개하지 않습니다.

 

코루틴 개념

CoroutineScope launch 또는 async를 사용하여 만든 코루틴을 추적합니다.

진행 중인 작업, 즉 실행 중인 코루틴은 언제든지 scope.cancel()을 호출하여 취소할 수 있습니다.

Android에서 일부 KTX 라이브러리는 특정 수명 주기 클래스에 자체 CoroutineScope를 제공합니다.

예를 들어 ViewModel에는 viewModelScope가 있고 Lifecycle에는 lifecycleScope가 있습니다.

하지만 디스패처와 달리 CoroutineScope는 코루틴을 실행하지 않습니다.

 

APP 자체적인 CoroutineScope를 만들어 앱의 특정 레이어에서 코루틴의 수명 주기를 제어해야 한다면 다음과 같이 만들면 됩니다.

class ExampleClass {

    // Job and Dispatcher are combined into a CoroutineContext which
    // will be discussed shortly
    val scope = CoroutineScope(Job() + Dispatchers.Main)

    fun exampleMethod() {
        // Starts a new coroutine within the scope
        scope.launch {
            // New coroutine that can call suspend functions
            fetchDocs()
        }
    }

    fun cleanUp() {
        // Cancel the scope to cancel ongoing coroutines work
        scope.cancel()
    }
}

취소된 scope는 코루틴을 더 만들 수 없습니다.

따라서 수명 주기를 제어하는 클래스가 제거되는 경우에만 scope.cancel()을 호출해야 합니다.

viewModelScope를 사용할 때 ViewModel 클래스는 ViewModel의 onCleared() 메서드에서 자동으로 범위를 취소합니다.

 

JOB

Job은 코루틴의 핸들러입니다. 

launch 또는 async로 만드는 각 코루틴은 코루틴을 고유하게 식별하고 수명 주기를 관리하는 Job 인스턴스를 반환한다.

다음 예와 같이 Job을 CoroutineScope에 전달하여 코루틴의 수명 주기를 추가로 관리할 수도 있습니다.

class ExampleClass {
    ...
    fun exampleMethod() {
        // 생명주기를 조절하면서 코리틴을 다룰수 있음 
        val job = scope.launch {
            // New coroutine
        }

        if (...) {
            // 위에서 시작된 코루틴을 취소할 수 있다. 스코프에 어떤 영향도 주지않는다.
            // 실행되는 코루틴을 취소한다. 
            job.cancel()
        }
    }
}

 

CoroutineContext

CoroutineContext는 아래와 같은 요소를 사용하여 코루틴의 동작을 정의합니다.

  • Job: 코루틴의 수명 주기를 제어합니다.
  • CoroutineDispatcher: 적절한 스레드에 작업을 전달합니다.(전달 및 작업을 실행 정지 및 재개한다.)
  • CoroutineName: 디버깅에 유용한 코루틴의 이름입니다.
  • CoroutineExceptionHandler: 포착되지 않은 예외를 처리합니다.

scope 내에 만들어진 새 코루틴의 경우 새로운 Job 인스턴스가 새로운 코루틴에 할당된다.

새로운 코루틴의 CoroutineContext 요소는 scope안 에서 상속됩니다.

새로운 CoroutineContext를 launch 또는 async 함수에 전달하여 상속된 CoroutineContext의 요소를 재정의할 수 있습니다. 

 

 

Job을 launch 또는 async에 전달해도 아무런 효과가 없습니다. Job의 새 인스턴스가 항상 새 코루틴에 할당되기 때문입니다.

class ExampleClass {
	// CoroutineContext 정의.
    val scope = CoroutineScope(Job() + Dispatchers.Main)

    fun exampleMethod() {
        // 위에서 정의한 스코프를 통해 코루틴을 실행하면 위의 CoroutineContext 상속받아
        // Main Dispatchers에서 실행된다. 
        val job1 = scope.launch {
            // New coroutine with CoroutineName = "coroutine" (default)
        }

        //  새로운 Dispatchers.Default로 재정의하고 이름도 재정의 했음.
        val job2 = scope.launch(Dispatchers.Default + "BackgroundCoroutine") {
            // New coroutine with CoroutineName = "BackgroundCoroutine" (overridden)
        }
    }
}

 

'Coroutine' 카테고리의 다른 글

Android의 코루틴 권장사항  (1) 2021.04.07
Kotlin 코루틴으로 앱 성능 향상  (0) 2021.04.07
Android의 Kotlin Coroutine  (0) 2021.04.07