루피도 코딩한다

[Coroutine Basics 4] Coroutine Cacelation 본문

Coroutine

[Coroutine Basics 4] Coroutine Cacelation

xiaolin219 2024. 1. 19. 16:44

코루틴 취소를 왜하냐?

  • 필요하지 않은 코루틴을 적절히 취소해 컴퓨터 자원을 아낄 수 있다

코루틴 취소 하는 방법 == CancellationException 발생

  • Kotlin 공식문서의 말을 빌리자면 *' A coroutine code has to cooperate to be cancellable' *라고 한다.
  • Cancellable 하다라는 것은 코루틴 블록 내부에서 CancellationException 이 발생가능한 구조여야 한다는 것이다.
  • CancellationException이 발생되는 방법은 아래와 같이 두가지가 있다.

[취소 방법 1] kotlinx.coroutines의 suspending function을 활용하자

  • kotlinx.coroutines의 suspending 함수들은 'cancellable'하다고 적혀있다.
    == 내부적으로 'CancellationException' 발생시킴
  • 예시로는 delay(), yield()와 같은 함수들이 있다
  • delay() 함수의 내부 구현을 보면, 현재 코루틴의 Job 상태가 canceled되었거나 completed 되었다면 CancellationException이 발생한다고 적혀있다 (아래 이미지 참고)
    1. Waiting 상태에서 외부에서 cancel을 호출하면 CancellationException()이 발생되고 취소됨
    2. 해당 job이 completed상태여도 CancellationException이 발생함. 그러니 이미 coroutine의 job이 완료가 된 상황에서 외부에서 cancel 코드를 호출하면 아무일도 일어나지 않는다는건 자명한 사실~
    image-20240119160230988

아니 그러면 delay(), yeild()같은거 안쓸때는 코루틴 취소를 어떻게 해야하냐???

  • 위 코드 블록을 통해 코루틴 내부에서 kotlinx.coroutinessuspending 함수가 없는 경우, 코루틴 외부에서 cancel()시키더라도 코루틴이 취소되지 않는다는 것을 알 수 있다.
  • main에서 cancelAndJoin()이라는 job을 취소하고 완료될때까지 기다리는 함수를 호출했음에도 불구하고 coroutine은 취소되지 않고 job: I'm sleeping 5 ... 까지 while문을 야무지게 끝까지 실행한다.

image

[취소 방법 2] 코루틴이 셀프로 자기 상태를 확인하고, 취소 요청을 받았다면 CancellationException 던지기

코루틴 내부에서 isActive라는 boolean 확장 프로퍼티에 접근 가능한데, 이 프로퍼티를 확인함으로써 취소 요청을 받았는지 안받았는지 확인할 수 있다. (isActivefalse이면 취소 요청을 받은거임 ㅎ)

그리고 취소 요청을 받았다면, Kotlinx.Coroutines 내부 suspend 함수들처럼 셀프로 CancellationException을 던지는 것이다.

따라서 위 코드 블록에서 while()문 내부에 if(!isActive) 라면 throw CanellationException()을 던지코드를 추가해보도록 하자

// 코루틴 셀프 취소 방법 1
while() {
  if (!isActive) { 
      throw CancellationException()
    }
  // 실행할 코드들..
}

// 코루틴 셀프 취소 방법 2
while(isActive){
  // 실행할 코드들..
}

// cf. isActive는 아래와 같이 CoroutineScope.kt 내부에 확장 프로퍼티로 정의되어 있음
public val CoroutineScope.isActive: Boolean
    get() = coroutineContext[Job]?.isActive ?: true
  • 자 그럼 위 코드 블록에서 isActive에 따른 처리만 해주면 코루틴 취소가 될까?
  • 원래 이런 질문은 안되니까 물어보는게 국룰~
    • 현재 runBlocking 내부의 코드는 main thread 하나에서만 실행되고 있다.
      (Dispatcher 따로 지정 안함 == 스레드풀 수동으로 지정 안함 == 메인스레드에서 다 실행됨)
    • launch로 시작하는 코루틴 코드는 일단 실행되지 않고 일단 pass
    • launch 블록 밑에 delay()를 만남 -> 양보가 일어나면서 launch 실행
    • launch 끝나야 cancel() 이 호출됨 (위 이미지 타임라인 참고)
  • 그러면 어떻게 가능하게 만들까?
    • launch 내부에 코루틴이 실행되는 '도중에' cancel()을 호출해야됨
    • '도중에'가 가능하려면 스레드 두개를 사용해서 동시에 동작시켜야함
    • 따라서 lanuch(Dispatchers.Default){} 이렇게 launch로 코루틴을 시작시킬때 다른 스레드에서 동작시키게끔 정해주는것이다image-20240119150510505
    • Dispatchers 종류 by GPT
      • *Dispatchers.Default: *기본 스레드 풀을 사용하여 CPU 바운드 작업을 처리합니다. 주로 CPU 작업에 사용됩니다.
      • Dispatchers.IO: I/O 작업을 처리하기 위한 디스패처입니다. 파일 읽기/쓰기, 네트워크 호출과 같은 블로킹 I/O 작업에 적합합니다.
        • Default를 썼을 때와 마찬가지로 잘 cancel된다
      • Dispatchers.Main: Android 및 JavaFX와 같은 UI 스레드에서 사용하기 위한 디스패처입니다. 주로 UI 업데이트와 관련된 작업에 사용됩니다.
        • Android 및 JavaFX의 환경에서만 사용한 Thread풀인듯하다. android dependecy가 없는 kt 파일에서 실행시키니 에러가 난다
      • *Dispatchers.Unconfined: *디스패처를 지정하지 않은 경우, 현재 실행 중인 코루틴과 동일한 스레드에서 실행됩니다. 하지만 일시 중지 및 재개되는 동안 스레드가 변경될 수 있습니다.
        • 위 코드에서 스레드풀을 Unconfined로 지정해주게 되면 메인 스레드에서만 동작하게 되므로 코루틴 취소가 일어나지 않는다
      • Dispatchers.Main.immediate: Android에서만 사용 가능한 특별한 디스패처로, 즉시 작업을 실행합니다. 일반적인 Dispatchers.Main과 유사하지만, 즉시 실행됩니다.
      • Dispatchers.kt 내부에 아래와 같이 CoroutineDispatcher를 구현한 객체들이 정의돼어 있다.image-20240119154504331
      • actual이라는 키워드 선언을 통해 각 플랫폼에 맞는 실제 구현이 제공되는 구조라고 한다. (actual 키워드가 expect 선언에 대응되어 사용되며, KMP와 관련있는 개념이다 Kotlin Docs)

정리하자면

  1. isActive() : 현재 코루틴이 활성화 되어 있는지, 취소 신호를 받았는지 확인
  2. Dispatchers.Default : 코루틴을 다른 스레드에 배정

두개의 스레드를 동시에 이용함으로써 Cacel을 시키는 구조이다.

'Cancellation is cooperative' 이제는 이 말을 이해할 수 있다

이렇게 코루틴 내부에서 CancellationException()이 발생되어야만 취소가 가능하며, 이를 '협력'해야 취소가 가능하다라는 말로도 나타내는듯 하다.

CacellationException()와 try-catch-finally

  • 만약 try에서 CacellationException()이 발생했는데, catch( e: CancellationExcpetion) 에서 Exception을 잡아버리면 어떻게 될까? 당연히 취소 되지 않는다!
  • 주로 의도가 있어서 cancel을 작동시켰을 테니까 catch 문은 작성을 안하는게 일반적인듯 하고
  • 대신, finally 에서 필요한 작업을 처리하는 방법이 있다고 한다.

끝!

Comments