Kotlin

[Kotlin] CoroutineScope

꾸준함. 2024. 10. 3. 16:16

CoroutineScope

  • Coroutine들에 대한 scope를 정의하며 scope에는 여러 Coroutine들 포함
  • 자식 Coroutine들에 대한 생명주기를 관리하며 자식 Coroutine들이 모두 완료되어야만 scope도 완료되었다고 처리
  • CoroutineScope의 coroutineContext에는 꼭 Job을 포함해야 함

 

 

1. CoroutineScope 함수

  • CoroutineScope 함수를 이용해 CoroutineScope 생성

 

 

부연 설명

  • 인자로 주어진 context에 Job이 포함되었는지 확인
    • 포함되어 있지 않다면 Job을 생성하여 추가하고 ContextScope에 전달
    • 여기서 ContextScope는 CoroutineScope의 단순한 구현체

 

 

부연 설명

  • EmptyCoroutineContext를 통해 빈 CoroutineScope를 생성
  • 다만, Job을 생성하여 ContextScope를 생성하기 때문에 JobImpl을 포함

 

2. Coroutine Builder

  • 코틀린에서 코루틴을 생성하고 실행하기 위한 기본적인 도구
  • Coroutine Builder는 CoroutineScope의 CoroutineContext와 인자로 전달받은 context를 merge 하여 newContext 생성
  • Coroutine을 생성하여 start하고 반환
    • CoroutineScope의 Job을 부모로 가짐
    • Coroutine Builder를 통해 생성된 Coroutine은 비동기 하게 동작

 

2.1 launch 코루틴 빌더

  • StandaloneCoroutine을 생성하고 start 하는데 isLazy가 참일 경우 LazyStandaloneCoroutine 생성
  • StandaloneCoroutine을 Job으로 반환하는데 외부에서 launch를 실행한 후 Job을 획득하여 cancel, join 등을 실행 가능

 

 

  • newCoroutineContext를 통해 CoroutineScope의 coroutineContext와 인자로 주어진 context를 merge

 

 

launch 코루틴 빌더 예제

 

 

부연 설명

  • CoroutineScope에 Coroutine Builder인 launch를 통해 코루틴을 생성하고 this로 접근
  • 비동기로 동작하기 때문에 join을 통해 완료될 때까지 지연

 

2.2 async 코루틴 빌더

  • DeferredCoroutine을 생성하고 시작하는데 isLazy가 참이라면 LazyDeferredCoroutine을 생성
  • DeferredCoroutine을 Deferred로 반환
    • Deferred는 Kotlin 코루틴에서 비동기 작업의 결과를 나타내는 객체
    • Deferred는 미래에 완료될 값을 나타내며, 이는 자바의 Future나 자바스크립트의 Promise와 비슷한 역할
    • Deferred는 Job을 상속하고 있기 때문에 join, cancel 뿐만 아니라 await을 통해 block이 반환하는 값에 접근 가능

 

async 코루틴 빌더 예제

 

 

부연 설명

  • CoroutineScope에 코루틴 빌더인 async를 통해 코루틴을 생성하고 this로 접근
  • 비동기로 동작하기 때문에 await을 통해 완료될 때까지 suspend 하고 값에 접근

 

3. AbstractCoroutine

  • 모든 코루틴은 AbstractCoroutine을 상속
  • 코루틴은 Job이며 Continuation이고 CoroutineScope

 

 

부연 설명

  • Job: 작업의 단위가 되고 start, cancel로 상태를 변경할 수 있으며 join으로 완료 시점 명시
  • CoroutineScope: 코루틴 빌더를 통해 자식 코루틴을 생성하고 자식 코루틴들의 생명주기를 관리

 

 

 

부연 설명

  • CoroutineScope에 코루틴 빌더인 launch를 통해 coroutine1 생성
  • coroutine1에서 자식 코루틴인 coroutine2 생성하며 이는 coroutine1이 CoroutineScope이기 때문에 가능한 것
  • job2의 join을 통해 완료될 때까지 지연

 

4. Coroutine Builder의 문제점과 해결 방법

 

4.1 launch 코루틴 빌더 문제점

  • 여러 job들이 완료되어야 하는 시점이 중요할 때 job을 받아서 직접 join을 실행해야 하지만 매번 여러 job들을 join 하는 것은 쉽지 않음
    • ex) job1, job2, job3은 정확히 step1 출력과 step2 출력 사이에 완료되어야 하는 케이스

 

 

4.1.1 launch 코루틴 빌더 문제점 - 해결 방법 1

  • 코루틴들을 묶는 코루틴을 생성하여 join
    • job1은 3개의 코루틴을 포함하고 join을 호출하여 지연시킨 후 3개의 코루틴이 모두 완료된 후 재개
    • 여전히 job1에 대한 join은 존재하지만 기존처럼 N번 join을 호출할 필요는 없어짐

 

 

4.1.2 launch 코루틴 빌더 문제점 - 해결 방법 2

  • CoroutineScope를 생성하여 step1과 step2 사이에 추가
    • CoroutineScope는 동기적으로 동작
    • CoroutineScope의 ScopeCoroutine이 launch의 StandaloneCoroutine을 자식으로 두기 때문에 launch들이 모두 완료되고 coroutineScope가 종료됨을 보장

 

 

부연 설명

  • CoroutineScope는 외부 scope의 context를 상속하고 job을 오버라이드하기 때문에 분리된 환경에서 자식 코루틴들의 생명주기를 관리하고 자식 코루틴이 모두 완료되고 coroutineScope 함수가 완료됨을 보장
  • ScopeCoroutine을 생성하여 block을 실행하기 때문에 ScopeCoroutine은 동기적으로 동작

 

coroutineScope

 

ScopeCoroutine

 

4.2 async 코루틴 빌더 문제점

  • 여러 deferred들이 완료되어야 하는 시점이 중요할 경우 모든 deferred에 await()을 호출해줘야 함
    • ex) deferred 1, 2, 3은 정확히 step 1 출력과 step2 출력 사이에 반환되어야 함

 

 

4.2.1 aysnc 코루틴 빌더 문제점 - 해결 방법

  • coroutineScope로 async를 감싸고 마지막에 await를 호출하여 결과를 합산할 경우 async 함수를 호출하는 순간부터 각각의 코루틴들을 시작
    • coroutineScope로 감싸고 async를 실행하기 때문에 포함하고 있는 async들만 종료되고 나면 coroutineScope도 종료

 

 

5. Job Cancellation

  • 코루틴은 기본적으로 협력적 취소(cooperative cancellation) 방식을 사용하며, 이는 코루틴이 취소 신호를 받아들여 스스로 취소되는 방식
  • 코루틴에서 Job은 코루틴의 생명 주기를 관리하며 launch나 async 같은 코루틴 빌더는 Job 객체를 반환하는데, 해당 객체들을 통해 코루틴을 취소할 수 있음
    • 코루틴 취소를 할 때는 Job.cancel() 메서드를 호출하면 됨
    • 취소 신호는 코루틴이 실행되는 동안 협력적으로 전달되며, 코루틴이 중단 가능한 상태에서 해당 신호를 감지했을 때 취소가 완료됨
    • 중단 가능한 지점은 suspend 함수 중에서 취소를 인식하고 처리할 수 있는 함수들이며 대표적인 예는 delay(), yield(), 그리고 채널 또는 플로우와 같은 비동기 스트림의 중단 함수

 

5.1 Root Coroutine에서 cancel을 호출하는 예제

  • Root Coroutine에서 cancel을 진행하고 join을 통해 모든 코루틴이 종료될 때까지 대기
  • job2, job3의 delay에서 모두 JobCancellationException 발생

 

 

5.2 Leaf Coroutine에서 cancel을 호출하는 예제

  • 종단에 해당하는 coroutine2와 coroutine3 중에 coroutine2를 job으로 받아 cancel
  • job2는 cancel 된 후 JobCancellationException 발생하지만 job3는 제대로 실행됨
  • Root Coroutine도 cancelled 상태가 아닌 것을 확인 가능

 

 

5.3 Leaf Coroutine에서 예외가 발생한다면?

  • coroutine3의 delay가 JobCancellationException을 던지면 Root Coroutine, CoroutineScope의 job 모두 cancelled 상태로 변경하며 이유는 Coroutine 예외 전파 규칙 내용을 참고

 

 

비고

 

1. launch 코루틴 빌더 vs CoroutineScope

  • launch는 비동기적으로 동작하는데 반해 coroutineScope는 동기적으로 동작
  • coroutineScope는 ScopeCoroutine을 생성하지만 ContinuatinoId를 오버라이드하지 않고 launch는 새로운 ContinuationId를 생성
  • 또한 coroutineScope는 결과를 반환하지만 launch는 Job을 반환

 

 

2. Coroutine 예외 전파 규칙

  • 코루틴에서 예외는 CoroutineScope에 따라 다르게 전파
  • 코루틴의 예외는 부모-자식 관계에 따라 전파되며, 자식 코루틴에서 발생한 예외는 부모 코루틴에 영향을 미침
    • launch 코루틴 빌더는 결과를 반환하지 않기 때문에, 예외가 발생하면 바로 부모 코루틴으로 전파되며 만약 예외를 잡아내지 않을 경우 부모 코루틴이 예외에 의해 취소됨
    • async 코루틴 빌더는 결과를 반환하는 Deferred 객체를 생성하기 때문에 예외가 발생해도 즉시 부모 코루틴으로 전파되지 않으며, await()을 호출할 때 예외가 발생

 

  • SupervisorJob을 사용하면, 자식 코루틴의 예외가 부모에게 전파되지 않고 독립적으로 처리될 수 있음
    • SupervisorJob을 사용하면 부모 코루틴이 자식 코루틴의 예외로 인해 영향을 받지 않으며, 다른 자식 코루틴들도 계속해서 실행

 

https://huisam.tistory.com/entry/kotlin-coroutine-exception

 

참고

패스트 캠퍼스 - Spring Webflux 완전 정복 : 코루틴부터 리액티브 MSA 프로젝트까지

반응형

'Kotlin' 카테고리의 다른 글

[Kotlin] CoroutineContext  (0) 2024.10.03
[Kotlin] 코루틴 (Coroutine) 기초  (0) 2024.10.02
[Kotlin] 코루틴 (Coroutine) 개요  (0) 2024.10.02
[Kotlin] lateinit과 by lazy의 차이점  (2) 2022.07.13