Shunz Android Dev Note

코틀린 코루틴에서의 취소 및 예외 처리 #1 - CoroutineScope, Job, CoroutineContext 본문

Kotlin

코틀린 코루틴에서의 취소 및 예외 처리 #1 - CoroutineScope, Job, CoroutineContext

_Shun_ 2023. 12. 2. 00:15

이 포스팅은 아래 게시글을 번역 및 일부 수정하여 작성하였습니다.

https://medium.com/androiddevelopers/coroutines-first-things-first-e6187bf3bb21


 

이 일련의 포스팅은 코루틴의 취소와 예외에 대하여 자세히 설명합니다. 취소 작업은 메모리와 베터리 수명을 낭비 할 수 있는 필요 이상의 작업을 하지 않도록 하는데 중요합니다. 적절한 예외 처리는 훌륭한 사용자 경험을 제공하는데 핵심 요소입니다. 본 포스팅은 이어서 작성할 다른 두 포스팅(2부 : 취소, 3부 : 예외)의 기초에 관하여 설명을 합니다. 예를 들어 CoroutineScope, Job 그리고 CoroutineContext입니다.

 

만약 영상을 보는것을 더 선호한다면 KotlinConf'19 에서 발표한 아래 영상을 참고해주세요.

https://youtu.be/w0kfnydnFWI

 

 

CoroutineScope

CoroutineScope는 launch() 또는 async() - CoroutineScope 의확장 함수 - 를 사용하여 생성한 모든 코루틴을 추적합니다. 진행중인 작업(실행중인 코루틴 함수들)은 언제든지 scope.cancel()을 호출하여 취소할 수 있습니다.

 

시작하고 싶을 때마다 CoroutineScope를 만들고 앱의 특정 계층에서 코루틴 lifecycle을 제어해야 합니다. Android와 같은 플랫폼에서는 이미 viewModelScopelifecycleScope와 같은 특정 lifecycle에 CoroutineScope를 제공하는 KTX 라이브러리가 있습니다.

 

CoroutineScope를 생성할 때 생성자에 대한 매개변수로 CoroutineContext를 사용합니다. 다음 코드를 사용하여 새로운 스코프와 코루틴을 생성할 수 있습니다.

// Job과 Dispatcher는 잠시 후에 설명할 CoroutineConText로 결합됩니다.
val scope = CoroutineScope(Job() + Dispatchers.Main)
val job = scope.launch {
    // 새 코루틴
}

 

 

Job

Job은 코루틴에 대한 핸들러입니다.  launch 또는 async에 의해 생성된 모든 코루틴에 대해 코루틴을 고유하게 식별하고 해당 라이프사이클을 관리하는 Job instance를 반환합니다. 위에서 본 것처럼 Job을 CoroutineScope에 전달하여 lifecycle을 유지할 수도 있습니다.

 

 

CoroutineContext

CoroutineContext는 코루틴의 동작을 정의하는 요소의 집합입니다. 다음과 같이 구성됩니다.

  • Job : 코루틴 라이프사이클을 제어
  • CoroutineDispatcher : 작업을 적절한 쓰레드로 보냄(할당)
  • CoroutineName : 코루틴의 이름을 명시. 디버깅할 때 유용함.
  • CoroutineExceptionHandler : 캐치되지 않은 예외를 제어. 이후에 작성될 포스트에서 다룰 예정.

 

새로운 코루틴의 CoroutineContext는 무엇인가요? 우리는 이미 Job의 라이프사이클을 제어할 수 있는 새로운 인스턴스가 생성될 것이라는 것을 알고 있습니다. 나머지 요소는 부모(다른 코루틴 또는 그것이 생성된 CoroutineScope)의 CoroutineContext에서 상속됩니다.

 

CoroutineScope는 코루틴을 만들 수 있고 코루틴 내부에 더 많은 코루틴을 만들 수 있으므로 암시적 작업 계층 구조가 만들어집니다. 다음 코드에서는 CoroutineScope를 사용하여 새 코루틴을 만드는 것 외에도 코루틴 내부에 더 많은 코루틴을 만드는 방법을 볼 수 있습니다.

fun main() = runBlocking {
    val scope = CoroutineScope(Job() + Dispatchers.Main)
    val job = scope.launch {
        // 부모의 CoroutineScope를 가지고 있는 새로운 코루틴 생성
        val result = async {
            // launch에 의하여 시작된 코루틴이 가지고 있는 CoroutineScope를 부모로 가지는 새로운 코루틴 생성 
        }.await()
    }
}

 

그 계층의 근원은 보통 CoroutineScope입니다. 우리는 그 계층을 다음과 같이 시작화 할 수 있습니다.

코루틴은 작업 계층 구조에서 실행. 부모는 CoroutineScope 또는 다른 코루틴이 될 수 있음.

 

Job lifecycle

Job은 여러 상태를 거칠 수 있습니다: New, Active, Completing, Completed, Cancelling, Cancelled.

상태 자체에 접근할 수는 없지만 Job의 속성에 접근할수는 있습니다: isActive, isCancelled, isCompleted.

Job 생명주기

 

코루틴이 활성화(Active) 상태인 경우, 코루틴의 실패 또는 job.cancel()을 호출하면 job을 Cancelling 상태(isActive = false, isCancelled = true)로 이동합니다. 모든 child jobs의 작업을 완료하면 코루틴은 Cancelled 상태가 되고 isCompleted = true라 됩니다.

 

부모 CoroutineContext

task hierarchy에서 각 코루틴에는 CoroutineScope 또는 다른 코루틴이 될 수 있는 부모가 있습니다. 그러나 코루틴의 결과적인 부모 CoroutineContext는 다음 공식을 기반으로 계산되므로 부모의 CoroutineContext와 다를 수 있습니다.

부모 컨텍스트 = Defaults + 상속된 CoroutineContext + arguments

 

어디서?

  • 일부 요소는 기본 값을 가지고 있습니다.
    CoroutineDIspatcher의 기본값은 Dispatchers.Default이고 "coroutine"은 CoroutineName의 기본값입니다.
  • 상속된 CoroutineContext는 CoroutineScope 또는 Coroutine을 만든 CoroutineContext입니다.
  • 코루틴 빌더에서 전달된 인자는 상속된 컨텍스트 해당 요소보다 우선합니다.

 

Note : CoroutineContexts는 "+" 연산자를 사용하여 결합할 수 있습니다. CoroutineContext가 요소의 집합이므로 플러스의 오른쪽에 있는 요소와 왼쪽에 있는 요소를 사용하여 새 CoroutineContext가 생성됩니다. 예) (Dispatchers.Main, "name") + (Dispatchers.IO, "name") = (Dispatchers.IO, "name")

 

코루틴의 이름인 "coroutine"은 Default값을 사용하고, Job, Dispatchers.Main, coroutineExceptionHandler는 인자로 넘기고 있습니다.

 

이제 새로운 코루틴의 부모 CoroutineContext가 무엇인지 알 수 있으므로, 실제 CoroutineContext는 다음과 같습니다.

새로운 코루틴 컨텍스트 = 부모의 CoroutineContext + Job()

 

위 이미지에 표시된 CoroutineScope를 사용하면 다음과 같은 새 코루틴이 생성됩니다.

val job = scope.launch(Dispatchers.IO) {
    // 새로운 코루틴
}

 

 

해당 코루틴의 부모 CoroutineContext와 실제 CoroutineContext는 무엇인가요? 아래 이미지의 솔루션을 참조해보세요.

 

CoroutineContext의 Job과 ParentContext의 Job은 같은 인스턴스가 될 수 없습니다. 코루틴을 생성하면 항상 새로운 Job 인스턴스가 생성됩니다.

 

부모 CoroutineContext가 scope의 CoroutineDispatcher 대신에 Dispatchers.IO를 가지고 있습니다. 이는 코루틴을 생성할때 사용한 Dispatchers.IO를 오버라이딩 했기 때문입니다. 또한, Job도 동일하지 않고 새롭게 생성이 되었기 때문에 색상이 다르게 나타나고 있습니다. (각각 빨강색, 녹색)

 


 

이제 이 시리즈의 파트2, 파트3를 통해 코루틴의 취소 및 예외처리에 대하여 자세히 알아보세요.