brunch

You can make anything
by writing

C.S.Lewis

by 서준수 Sep 11. 2023

코루틴 Job

코틀린 코루틴 (7)

코루틴 Job


구조화된 동시성을 이야기할 때 코루틴 취소에 관한 언급이 있었다. 그렇다는 것은 취소가 가능하다는 말이다. 그럼 궁금점이 생긴다. 어떻게 하는 걸까?


제목이 코루틴 Job이니까 Job이라는 녀석을 사용해서 취소를 하나 보네? 그럼 코루틴 취소를 하는 방법은 Job을 이용하는 것이겠군!


위와 같이 생각했다면 후하게 매겨도 50점이다. 코루틴 취소에 관한 이야기가 주제가 아니라 Job이 주제인만큼 취소 그 자체에 포커스를 맞추면 안 된다. Job을 이용해서 취소를 할 수는 있다. 하지만 모든 코루틴에 관해서 그런 것은 아니다.


아 그래? 그럼 또 추측을 해볼까? 취소를 할 수 없는 코루틴도 존재하는구나!


이것도 50점이다. 취소 불가능하게 만들 수 있는 부분도 있다. 하지만 Job이 아닌 다른 객체를 통해서 취소해야 하는 코루틴도 있다. 다시 이전의 기억을 되짚어보자. 코루틴 빌더를 기억하는가? 코루틴 빌더는 하나의 종류만 있는 것이 아니다. 그렇다면 이제 합리적인 추측을 해볼 수 있다.


Job을 사용하면 코루틴 빌더 중 한 녀석이 생성한 코루틴을 취소할 수 있겠구나. 아 그리고 취소에만 집중할 것이 아니니까 Job은 취소 외에도 뭔가 다른 임무를 수행할 것 같아!


이 정도가 정리되었다면 한결 이야기가 쉬워진다. 그래서 Job이 뭔데?


일단 놀랍게도 여기까지 오는 동안 Job을 몇 번이나 만들었다. Job은 코루틴 빌더인 launch를 통해 생성된다. 그러니까 launch를 작성했을 때마다 생성되었다는 것이다. launch의 구현부를 보라. 리턴 타입이 Job이다.



launch를 통해 코루틴을 생성했을 때마다 Job 객체가 반환되고 있었던 것이다. 단지 지금까지는 해당 객체를 받아와서 별도의 작업을 시도하지 않았을 뿐이다. 그럼 한번 해보자.

job이란 변수를 하나 만들어서 launch를 통해 생성된 Job 객체를 담았다. 그 후 cancel()을 통해 launch에 의해 생성된 코루틴을 취소한다. 그렇다면 실행 결과는 어떻겠는가? Hello, 가 출력된 후 코루틴이 취소되어 World! 는 출력되지 못하고 끝난다.


이제 구조화된 동시성에서 언급했던 부모 코루틴이 취소되면 자식 코루틴도 취소되는 경우도 간단하게 확인해 볼 수 있다.

부모 코루틴을 취소했을 때 자식 코루틴이 취소되지 않는다면 실행 결과는 Hello, Kotlin! 이 되어야 할 것이다.


코루틴이 정상적으로 취소되었다면 CancellationException 발생한다. 엥? 정상적으로 취소되었는데 왜 예외가 발생하지? 그 이유에도 이런저런 사유가 있지만 지금 굳이 그것까지 알아야 할까 싶다. 쉽게 생각하자면 취소를 했는데도 불구하고 취소되기 전까지 완료된 작업들이 예외적으로 완료가 된 것이라는 관점으로 보면 된다. 아무튼 예외가 발생한 것이다. 그래서 이 경우엔 정상적인 취소라고 본다. 대신에 다른 예외로 코루틴이 취소가 된다면 그건 정상적인 취소가 아니기 때문에 코루틴의 작업이 실패했다고 봐야 한다.


예외의 발생 여부는 다음과 같이 확인할 수 있다.

그런데 위의 예제 실행 결과가 조금 신경 쓰인다.

Hello,
Done!
Cancelled!

취소가 된 후에 완료하고 싶은데 Done! 이 먼저 출력되었다. 후후.. 이제 이 정도야 쉽지! println("Done!") 전에 delay(100L) 정도 선언해 주면 해결!


원하는 출력 형태는 얻을 수 있는 방법이다. 하지만 delay(10L)을 줘도 되는데 그럼 불필요하게 90ms 느리게 된 것이다. 즉 이 방법은 최선의 방법이 아니라는 것이다. 10ms만 기다려도 취소까지 모두 처리된다. 그럼 최선은 무엇인가? launch 내부가 완료될 때까지만 기다리면 된다.


그걸 어떻게?

앞에서 Job은 취소 외에도 뭔가 다른 임무를 수행할 것 같다고 추측했듯이 이것도 Job을 통해 할 수 있다. 바로 join()을 사용하면 된다. join()은 해당 코루틴이 완료될 때까지 기다리는 역할을 한다. 결과적으로

println("Done!") 전에 job.join()을 추가하면 다음과 같은 실행 결과를 얻을 수 있다.

Hello,
Cancelled!
Done!


여기까지 확인해 본 Job은 도대체 뭘 하는 녀석인가?

1. 코루틴을 취소할 수 있다.
2. 코루틴이 완료될 때까지 기다릴 수 있도록 한다.

이런 것들을 좀 더 추상화시킬 수 없을까? 취소, 완료 이런 것들을 좀 더 추상적인 표현으로 바꾸면 무엇으로 바꿀 수 있을까? 상태라고 할 수 있다. 이렇게 상태를 제어하는 역할을 하는데 혹시 확인하는 역할을 못할까? 한다. Job.kt 내부에 주석으로 설명된 내용을 보면 다음과 같이 크게 여섯 가지 상태를 가진다. 그리고 세 가지 프로퍼티에 각 상태에 따른 특정한 값이 할당된다.



상태가 변하는 과정에 대한 도식화도 제공한다. (이건 깔끔하게 다시 그려보는 것이 좋겠다.)


위의 내용을 참고해서 여러 가지 코드를 작성해 보고 결과를 예측해 보면서 확인하는 과정을 거치면 많은 도움이 될 것이다. 예를 들면 아래 코드의 실행 결과를 예측해 보고 join()을 제거했을 때는 어떤 결과가 나올지 생각해 보는 식이다.

isCancelled가 true이고 isCompleted가 false인 경우는 Job이 취소되었지만 아직 완전히 종료되지 않은 경우이다. 이것은 Job이 취소 요청을 받았지만, 취소 처리가 완료되지 않았을 때 발생할 수 있다.


요약하자.

- Job은 launch를 통해 생성된다.
- Job은 코루틴을 취소할 수 있다.
- Job은 코루틴이 완료될 때까지 기다릴 수 있도록 한다.
- Job은 코루틴의 상태를 제어하고 추적한다.
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari