🚨 이번 강은 영상 없이, 본 포스트로 Scheduler를 다룹니다.
다소 어려운 강일 수 있으므로, 나머지 챕터들을 완료하신 다음 보셔도 좋습니다.
(실습예제에 나오지 않습니다.)
Scheduler
ReactiveX의 요소들은 사용되는 언어마다 형태와 사용법에 있어 다소 차이가 있습니다.
그중에서도 스케줄러는 스레드에 관련된 것인 만큼,
언어나 환경마다 사용법을 따로 공부해야 할 상세부분에서 차이가 크기 때문에
필요시 내용들을 추가할 수 있도록 영상이 아닌 페이지 게시물로 따로 정리했습니다.
스케줄러를 간단이 설명하자면
옵저버블이나 연산자, 구독자가 멀티스레딩 환경에서 어느 스레드상에 실행될지를 정하는 것입니다.
- 🚀_ 지금 뭐가 진행되고 있든 이 작업을 지금 당장 실행해버릴지
- 🐇_ 현재 진행되고 있는 작은 작업을 마치는대로 최대한 빨리 실행할지
- 🐌_ 느긋하게 맨 뒤로 밀어서, 하고 있는 일들을 다 마치고 실행할지
- ⏰_ 특정 시간을 정해서 때가 되면 실행할지
이런 걸 정해주는게 스케줄러인거죠.
지정할 수 있는 스케줄러의 종류는 언어마다의 스레딩 환경에 따라 다릅니다.
🔗 공식 페이지에서 언어별 명세를 펼쳐보시면 RxJava 등과 같이
사용 가능한 스케줄러와 자세한 설명이 기재된 것들도 있지만
RxKotlin 등과 같이 TBD(이후 작성될 것) 상태인 것들도 있습니다.
후자의 경우 각각의 도큐먼트를 따로 검색해보는 수고가 필요합니다. 😢
ReactiveX의 연산자들은 각각 기본적으로 설정된 스케줄러('스케줄러 없음' 포함)가 있습니다.
🔗 공식 페이지의 언어별 명세에서 RxJava란을 펼쳐보시면 연산자들에
각각의 특성에 적합한 스케줄러가 기본으로 부여되어 있는 것을 보실 수 있습니다.
예를 들어
- debounce처럼 콜백이 이벤트 루프를 돌아 지연 처리되는 연산자는 computation
- timestamp나 timeInterval처럼 발행 즉시 구독자에게 넘겨지는 연산자는 immediate
- repeat처럼 대기중인 큐가 다 처리된 다음 실행되는 연산자에는 trampoline
위와 같이 부여된 것을 볼 수 있죠. (바로 윗부분에 각각에 대한 설명이 있습니다.)
RxJS의 경우 이 페이지에서 각 스케줄러들을 확인할 수 있습니다.
RxJS용 스케줄러 | 설명 |
---|---|
null | 스케줄러 없음. 동기적으로 또는 재귀적으로 사용되는 연산자에 사용 |
queueScheduler | 새 작업을 현재의 작업(task) 대기줄 맨 끝에 세움. 반복 연산자에 사용 |
asapScheduler | Promise에 사용되는 것과 동일 - 현 소작업(microtask)이 끝나고 그 다음 소작업을 하기 전 실행. 비동기 작업에 사용 |
asyncScheduler | setInterval과 함께 사용됨. 시간 관련 연산자에 사용 |
animationFrameScheduler | 브라우저가 내용을 새로 그리기(repaint) 전 실행됨. 부드러운 애니메이션을 위해 사용 |
이러한 스케줄러들을 파이프에 적용하기 위해 언어들마다 공통적으로 사용되는 연산자들로 아래 둘이 있습니다.
연산자 | 설명 |
---|---|
SubscribeOn | 옵저버블 또는 이를 처리할 연산자를 실행할 스케줄러 지정 |
ObserverOn | 구독자에게 알림을 보낼 때 사용할 스케줄러 지정 |
아래의 RxJS 예제를 통해 간략히 스케줄러를 알아봅시다.
<script src="https://unpkg.com/@reactivex/rxjs/dist/global/rxjs.umd.js"></script>
const { of, asyncScheduler } = rxjs
const { subscribeOn, observeOn, tap } = rxjs.operators
const tapper = x => console.log(`${x} IN`)
const observer = x => console.log(`${x} OUT`)
of(1, 2, 3).pipe(
tap(tapper),
subscribeOn(asyncScheduler)
).subscribe(observer)
of(4, 5, 6).pipe(
tap(tapper),
).subscribe(observer)
of('A', 'B', 'C').pipe(
tap(tapper),
observeOn(asyncScheduler)
).subscribe(observer)
of('D', 'E', 'F').pipe(
tap(tapper),
).subscribe(observer)
위의 예제에서 숫자 1, 2, 3을 발행하는 스트림과 문자 'A', 'B, 'C'를 발행하는 스트림에,
각 발행물을 현재의 마이크로태스크 다음에 발행하도록 하는 asyncScheduler를 적용했습니다.
때문에 각각은 4, 5, 6,과 'D', 'E', 'F'보다 다음에 나오죠.
차이가 있다면, 1, 2, 3은 subscribeOn을 써서 구독,
즉 옵저버블이나 연산자가 실행되는 시점부터 해당 스케줄러를 지정했습니다.
그리고 'A', 'B', 'C'는 observeOn을 사용해서
이들이 구독자에게 전달되는 시점만 async로 동작하도록 했죠.
때문에 전자는 tap('~ IN')되는 동작까지 모두 4, 5, 6보다 늦게 나온 반면
후자는 tap 부분은 먼저 출력되고
subscribe('~ OUT')되는 부분만 'D', 'E', 'F'보다 나중에 출력된 것입니다.
연산자들은 각각이 이미 적절한 스케줄러가 부여되어 있으므로
subscribeOn으로 이들을 굳이 지정하기보다는,
구독자가 이를 받아 작업을 수행하는 시점을 정하는
observeOn이 실무에서 보다 활용될 것입니다.
자바스크립트처럼 상대적으로 덜 부각되는 환경도 있지만
iOS 앱 개발을 위한 스위프트 등에서와 같이
각 작업이 어떤 스레드에서 진행되는가(ex: UI가 업데이트되는 큐)가 중요한 곳에서는
이 스케줄러를 공부해 둘 필요가 있습니다.
본 강좌에서 모든 언어들마다의 스케줄러를 설명할 수는 없지만,
ReactiveX의 다른 요소들의 개념과 그 사용법을 익히신 후 스케줄러를 조사해보시면
자기 언어에서의 적절한 스케줄러 사용법도 어렵지 않게 익히실 수 있을 것입니다.