티스토리 뷰

UWP & Windows App

Reactive Extension part1

kaki104 2014. 8. 24. 20:23
반응형

Reactive Extension(Rx)! - 포스트에서는 Rx라고 줄여서 이야기한다.

 

이번 포스트에서는 Rx의 정체가 무엇인지, 뭐가 다른 것인지에 대해서 알아 본다.

 

처음 Rx를 알게된 것은 몇 년전 헬렐레 셈을 통해서 배우게 되었는데, 한번 배운 후로 가끔 맛보기만 해봤지, 이걸 본격적으로 사용해야겠구나 하는 생각은 하지 못했다. 왜냐하면, 큰 개념은 이해가 되는데, 새부적으로 들어가면서, 머리에 부하가 걸리면서, 갑자기 코드가 난해해지기 시작하고, 해독 불가 상태에 빠지기 때문이였다.

 

그러다.. 시간이 흐르고 흘러 어느날 갑자기 이넘을 사용해야할 것 같은 불길한 기운이 뒤통수를 치는 느낌을 받았다.

그 불길한 기운의 사연은 이렇게 시작된 것이다. 

만들려고 하는 PCL이 Windows 8.1과 Windows Phone 8.1만을 지원하는 Unversal용 PCL인데, 기존에 내가 사용하고 있던 Prism.PubSubEvent Nuget package가 새로만든 PCL에서는 사용이 불가능하고, Rx Nuget package는 사용이 가능하기 때문에 기존에 만들어 놓았던 부분을 Rx로 바꾸어야 하는 상황이 발생한 것이다...

 

 

"개인 이기주의의 표본 다운 생각이얌! 그럼 이번에도 초보자용은 아닌거냐?"

 

"나도 초보다."

 

"잘됐군. 그럼 나도 읽고 공부를 해야겠네. 자세하게 쓰는거냠?"

 

"아마도.."

 

"아마도?"

 

"나한테도 도움이 될 수 있도록 좀 자세하게 써놔봐~"

 

"그건 모르겠다"

 

"왜???" @,.@a

 

"..."

 

앱 개발 이해도가 바닥을 기는 악마넘이 이해 할 수 있도록 쓰기에는 내용 자체가 난해한 것이 있어서 풀어서 설명하는데도 한계가 있을 것 같은데..저 녀석을 어떻게 이해 시켜야 할지..흠흠..

 

"흠흠..노력하지"

 

"ㅇㅋ ㅎㅎㅎ"

 

주먹은 법보다 가깝다.. 쿨럭,..나의 영혼없는 대답을 듣고, 혼자 느끼한 웃음을 흘리며 눈알을 굴리고 있는 악마넘! 어쩌다가 저넘이랑 엮여서 내 인생의 평탄길이 울퉁불퉁 자갈길로 변했는지...에휴.. 성격 좋은 내가 참고 살아야 하는데도 쉽지 않은 것 같다.

 

 

1. Rx 란?

 

구글에서 rx로 입력하면 Reactive Extensions (Rx) - MSDN - Microsoft라는 곳이 나온다. 이 곳이 Rx홈이다. 이곳으로 이동한다.

 

The Reactive Extensions (Rx)...

http://msdn.microsoft.com/en-us/data/gg577609.aspx

 

맨 처음에 나오는 문장을 살펴 보면..

 

The Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences and LINQ-style query operators. Using Rx, developers represent asynchronous data streams with  Observables, query asynchronous data streams using  LINQ operators, and parameterize the concurrency in the asynchronous data streams using  Schedulers. Simply put, Rx = Observables + LINQ + Schedulers.

 

Rx 비동기와 이벤트 베이스 프로그램에서 사용하는 옵저버블 스퀜스와 LINQ-스타일 쿼리 오퍼레이터의 구성 라이브러리를 이야기한다. ....중간은 대충 패스.. 마지막에 Simply put, Rx = Observables + LINQ + Schedulers라는 내용이 핵심이다.

 

몇일 전에 이 내용을 다시 읽어 보고서야, 이것이 무엇을 의미하는 것인지를 알게되었다. Rx를 파악하기 위해서는..

 

1) Observer Design Pattern

http://msdn.microsoft.com/en-US/library/ee850490.aspx

 

2) LINQ

 

3) Using Schedulers - Thread

http://msdn.microsoft.com/en-us/library/hh242963(v=VS.103).aspx

 

위의 3가지에 대한 어느 정도의 이해가 필요했다라는 것이 나의 결론이다. 그렇다고, 위의 3가지를 모르면 사용하지 못하는가? 꼭 그런 것은 아니다~ 다만 응용을 하기 쉽지 않을 뿐이니 이미 나와있는 내용은 사용이 가능 하리라 생각한다.

 

 

"뭐 나에게는 해당 사항이 없네..으흐흐"

 

"..."

 

"어떻게 생각해?"

 

"닭이 먼저냐 달걀이 먼저나?"

 

"머냐 인간?? 갑자기 뼈다귀 침바르는 소리를 하는 거냐?"

 

"반응은 해야하고, 답은 않나와서 해본 소리다."

 

"............다크 수피어!"

 

내 주위에 생겨난 어둡고 뾰족한 시커먼 창이 날 죽일듯이 날라온다..내가 싫어하는 공격 중에 하나인데..도망이 상책인 듯..칫!! 푸다다다닥!!!!

 

 

2. 백마디 말보다 주먹 한방이 더 직빵이다.

 

Using Reactive Extensions in Silverlight

http://www.silverlightshow.net/items/Using-Reactive-Extensions-in-Silverlight.aspx

 

말로 주저리 주저리 설명해도 이해하는데는 크게 도움이 않될 것이라 생각해서, 일단 간단한 셈플을 하나 만들어 보겠다. 지금 만들 셈플은 위의 포스트에 있는 내용 중에 하나를 응용한 것이다.

 

 

3. RxSample.Universal project 생성

 

포스트에서는 Visual Studio 2013 Update 3버전을 사용하고 있다. 그 이하 버전 특히 Visual Studio 2010 버전으로 테스트를 해 보고 싶은 개발자는 WPF나 Silverlight로 만들어서 테스트를 하면 가능 할 것이다.

 

 

위와 같이 프로젝트를 생성한다.

 

 

4. Windows 8.1 프로젝트 쪽만 작업 한다.

 

4.1 MainPage.xaml

wallpaper031-1920x1200.jpg이미지는 자신이 가지고 있는 적당한 이미지를 사용한다.

 

    <Grid x:Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Canvas>
            <Image x:Name="ImgImage" Height="200" Width="300" Source="wallpaper031-1920x1200.jpg" Stretch="UniformToFill"/>
        </Canvas>

    </Grid>

 

4.2 MainPage.xaml.cs

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            grid.PointerMoved += grid_PointerMoved;
        }

        void grid_PointerMoved(object sender, PointerRoutedEventArgs e)
        {
            var pos = e.GetCurrentPoint(this);
            Canvas.SetLeft(ImgImage, pos.Position.X);
            Canvas.SetTop(ImgImage, pos.Position.Y);
        }
    }

 

실행해본다.

 

 

아주 간단한 프로그램으로 그리드에 마우스가 움직이면 그 위치로 이미지 컨트롤을 이동시키는 것이다. 잘 동작하는 것을 볼 수 있을 것이다.

이 간단한 것을 Rx를 이용해서 변경해 보자. 처음부터 너무 어려운 것을 변경하면, 자포자기 상태에 빠질 것 같기 때문에 아주 아주 단순 심플한 녀석을 처음으로 선택하게 되었다.

 

 

5. Rx 적용

 

Nuget package에서 rx를 입력해서 검색 후 설치한다.

 

 

 

6. Windows 8.1 프로젝트만 수정한다.

 

6.1 MainPage.xaml은 변경사항이 없다.

6.2 MainPage.xaml.cs

 

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            //grid.PointerMoved += grid_PointerMoved;

 

            IObservable<EventPattern<PointerRoutedEventArgs>> mouseMoveEvent = Observable.FromEventPattern<PointerRoutedEventArgs>(grid, "PointerMoved");
            mouseMoveEvent.Subscribe(evt =>
            {
                var pos = evt.EventArgs.GetCurrentPoint(this);
                Canvas.SetLeft(ImgImage, pos.Position.X);
                Canvas.SetTop(ImgImage, pos.Position.Y);
            });
        }

        void grid_PointerMoved(object sender, PointerRoutedEventArgs e)
        {
            var pos = e.GetCurrentPoint(this);
            Canvas.SetLeft(ImgImage, pos.Position.X);
            Canvas.SetTop(ImgImage, pos.Position.Y);
        }
    }

 

오잉? 이게 무슨 코딩일까?? 생성자에서 별 이상한 짓을 하고 있는데...이 코딩에 대한 설명은 아래에서 설명하도록 하겠다. 실행해보면 아까와 동일한 결과가 나오는 것을 알 수 있다. 화면에 이미지가 잘 돌아 다닌다. 허무한가?? 하하..;;; 하지만, 반전(?)이 있으니 끝까지 가보도록 하자.

 

6.3 MainPage.xaml.cs

 

        public MainPage()
        {
            this.InitializeComponent();

            //grid.PointerMoved += grid_PointerMoved;

            //FromEvent가 있고 FromEventPattern이 있음
            IObservable<EventPattern<PointerRoutedEventArgs>> mouseMoveEvent = Observable.FromEventPattern<PointerRoutedEventArgs>(grid, "PointerMoved");
            IObservable<EventPattern<PointerRoutedEventArgs>> mousePressedEvent = Observable.FromEventPattern<PointerRoutedEventArgs>(grid, "PointerPressed");
            IObservable<EventPattern<PointerRoutedEventArgs>> mouseReleasedEvent = Observable.FromEventPattern<PointerRoutedEventArgs>(grid, "PointerReleased");
            var dragDropEvent = mouseMoveEvent.SkipUntil(mousePressedEvent).TakeUntil(mouseReleasedEvent).Repeat();
            dragDropEvent.Subscribe(evt =>
            {
                var pos = evt.EventArgs.GetCurrentPoint(this);
                Canvas.SetLeft(ImgImage, pos.Position.X);
                Canvas.SetTop(ImgImage, pos.Position.Y);
            });

        }

 

위와 같이 수정 한 후 실행을 해보자! 드래그앤드롭과 동일한 동작을 하는 것을 볼 수 있다. 위의 코딩을 이벤트 핸들러를 사용하는 방법으로 변경한다면 아래와 같이 할 수 있을 것이다.

 

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            grid.PointerMoved += grid_PointerMoved;
            grid.PointerPressed += grid_PointerPressed;
            grid.PointerReleased += grid_PointerReleased;

 

            //FromEvent가 있고 FromEventPattern이 있음

            ...
        }

 

        private bool _canMove;

        void grid_PointerReleased(object sender, PointerRoutedEventArgs e)
        {
            _canMove = false;
        }

        void grid_PointerPressed(object sender, PointerRoutedEventArgs e)
        {
            _canMove = true;
        }

        void grid_PointerMoved(object sender, PointerRoutedEventArgs e)
        {
            if (_canMove == false) return;
            var pos = e.GetCurrentPoint(this);
            Canvas.SetLeft(ImgImage, pos.Position.X);
            Canvas.SetTop(ImgImage, pos.Position.Y);
        }
    }

 

위에 작성한 방법보다 더 좋은 방법이 있다면, 가차 없이 리플로 남겨주기 바란다.

주석으로 막아 놓은 이벤트 연결부를 풀고, Rx처리부를 주석으로 막은 후 실행 한다. 하지만.. 아직까지도 눈에 보이는 차이점은 발견 할 수 없다.

 

 

7. 기존 코딩으로는 않되고, Rx에는 잘되는 것

아직까지는 눈으로 보이는 부분에서 큰 차이점을 찾기는 어렵다. 그래서, 확실한 차이점을 강제로 만들어 보도록 하겠다.

 

이벤트 처리에 Delay를 넣어 보자

 

        async void grid_PointerMoved(object sender, PointerRoutedEventArgs e)
        {
            if (_canMove == false) return;

            await Task.Delay(1000);
            var pos = e.GetCurrentPoint(this);
            Canvas.SetTop(ImgImage, pos.Position.Y);
            Canvas.SetLeft(ImgImage, pos.Position.X);
        }

 

실행한 후 마우스 클릭 후 마구 마구 돌려보자. 어느 순간 이미지가 마우스를 따라서 움직이지 않는 경우가 발생할 것이다.

 

다음은 Rx 처리 루틴을 수정한다.

 

            dragDropEvent.Subscribe(async evt =>
            {
                await Task.Delay(1000);
                var pos = evt.EventArgs.GetCurrentPoint(this);
                Canvas.SetTop(ImgImage, pos.Position.Y);
                Canvas.SetLeft(ImgImage, pos.Position.X);
            });

 

위와 같이 수정 후 실행해서 마우스를 돌려보자. 물론 이때는 이벤트 연결시키는 코드를 주석으로 막아 놓고 해야한다.

Rx는 마우스의 움직임을 끝까지 추적하는 것을 볼 수 있을 것이다. 하하하 이제서야 설명을 시작 할 수 있을 것 같다.

 

 

"음 이게 반전 코딩?? 내 컴은 슈퍼컴이라 그런지 둘다 잘 되는데??"

 

"내 컴퓨터가 기준이니, 슈퍼컴에서 되는 것은 나의 예상 밖이다."

 

"음..."

 

갑자기 말을 않하고 생각에 빠져있는 저 모습... 웬지 2초 후의 미래가 갑자기 어두워 질 것 같은 불길함이..

 

"인간! 너 컴퓨터도 슈퍼컴으로 바꿔줘??"

 

"정말!?"

 

"뻥인데?? ㅎㅎㅎㅎㅎ"

 

옥상에서 떨어지는 돈 잡을려다가 바닥에 떨어져서 갈비뼈 부러진듯한 배신감이..

 

 

8. Pull-based와 Push-based architectures

 

Curing Your Event Processing Blues with Reactive Extensions (Rx)

http://channel9.msdn.com/Events/TechEd/Europe/2012/DEV413

 

위 예제에서 grid의 PointerMoved, PointerPressed, Pointerreleased 이렇게 3개의 이벤트를 이벤트 핸들러와 연결해서 사용하는데 이러한 방식을 Event-based, 혹은 Event-driven방식이라고 이야기하는데, 이벤트가 발생하고 그 이벤트에 연결된 이벤트 핸들러를 호출해서 사용하는 것을 Pull-based architectures라고 이야기한다.

일반 응용 프로그램의 기본적인 사용방식이며, 웹 프로그램에서도 대부분 많이 사용하고 있는 방식이다.

 

Rx는 Push-based architectures를 사용한다. 평소에 observable 객체가 대기를 하고 있다가, 이벤트나 변경 사항이 발생하면, 자신을 subscriptiion하고 있는 곳으로 push-notification 해준다. 쉽게 스마트 폰에서 카톡 메시지가 도착하면 폰에서 '카톡왔네~'라는 소리가 나는 것을 생각하면 된다.

 

위의 예제에서 grid의 PointerMoved, PointerPressed, Pointerreleased 3개의 이벤트를 감시하는 mouseMoveEvent, mousePressedEvent, mouseReleasedEvent 3개의 observable 객체가 존재하고, 이 객체가 대기를 하고 있다가, grid에서 해당 이벤트가 발생하면, Subscribe으로 지정되에있는 Action을 실행하게 된다. 이러한 일련의 과정을 Event Streams이라 하며 구성은 아래와 같다.

 

8.1 Event Streams구성

 

 

생산자(Producers)는 observable sequences 객체가 되는 것이고, 생산자가 .Net Events, WinRT Events, sensor APIs, APM methods, tasks, etc 등과 연결되어서 변경 사항을 observers에게 push 작업을 실행한다.

소비자(Consumers)는 observers로 Subscribe을 통해 observable과 연결되고, push를 받아서 그에 해당하는 처리를 담당하게 된다.

 

 

9. .Net Event의 한계

 

 

9.1 Can't pass around : 이벤트 자체를 객체로 인식하지 않기 때문에 다른 곳으로 전달할 수 없다.

9.2 Hidden data source : 이벤트에 포함된 데이터를 사용하기 위해서는 이벤트 핸들러를 통해야지만 내용을 알 수 있다.

9.3 Lack of composition : 이벤트 핸들러를 비동기로 실행하면서 랙이 발생한다.

9.4 Hard resource maintenance : 효율적인 메모리 관리를 위해서는 연결한 이벤트 핸들러를 해재해 주어야 하는데, 위의 경우에는 이벤트 핸들러를 해제할 수 없다.

 

 

10. Observable Sequences가 구세주다!

 

 

10.1 Source of Quotes : 이벤트 핸들러를 사용하지 않고도, 데이터의 타입이 Quote라는 것을 알 수 있다.

10.2 Objects can be passed : 이벤트를 IObservable<T> object로 처리하기 때문에 다른 곳으로 보내거나, 저장도 가능하다.

10.3 Can define query operators : 이벤트를 IObservable<T> object로 관리 하기 때문에 LINQ query로 필더링, 데이터 형(type)변환 등의 많은 기능을 추가할 수 있다.

10.4 Easy resource maintenance : Subscribe을 할 때 발생하는 Disposable 객체를 Dispose() 시킴으로서 Unsubscribe을 할 수 있다.

 

 

11. .Net Events와 Observables의 장단점 비교

 

.Net Events  Observables 
코드 중심  데이터 중심 
디자인(xaml)에 표현 가능 디자인(xaml)에 표현 불가 
클래스 생성 불가 클래스 생성 가능 
구성방법 변경 불가 다양한 구성방법 변경 가능 
가벼움 약간 무거움 
단단한 실행 모델  Expression Tree로 번역됨

 

붉은 색 글씨는 불리한 점이고 녹색은 강점임

 

 

12. Rx에 대한 대략적인 사항들만 살펴 보았다.

 

더 자세한 것은 앞으로 part2, part3... 포스트들을 지속적으로 보면 차츰 알 수 있지 않을까 한다. 우선 이 포스트에서는 Rx가 무엇인지 궁금증을 느끼는 정도면 충분하지 않을까 싶다.

 

지금까지의 내용을 한마디로 정의 하면, Rx = Observables + LINQ + Schedulers 이다. Observable를 이용해서 push를 구현하고, 다양한 조건을 주기 위해 LINQ를 사용하며, Scheduler를 이용하여 다이나믹한 실행 환경이 만들어 사용한다. 이러한 내용을 기준으로 위의 소스에서 어떤 차이가 생겨서, 결과가 다르게 나타나는지 생각해보자.

 

 

"생각을 해봤는데.."

 

"ㅇㅇ?"

 

"모르겠는데??"

 

"ㅇㅇ"

 

"뭐가 ㅇㅇ이야! 제대로 설명을 해줘야 할 것 아냐! 너 혹시 너도 모르는 거지??"

 

"..."

 

푸다다닥=3=3=3

 

 

13. 소스 

RxSample.Universal.zip
다운로드

 

반응형

'UWP & Windows App' 카테고리의 다른 글

ReactiveUI part1  (0) 2014.09.29
Reactive Extension part5  (2) 2014.09.19
Reactive Extension part4  (0) 2014.09.14
Reactive Extension part 3  (2) 2014.09.13
Reactive Extension part2  (0) 2014.08.25
댓글