|
Start ... rea | ||||
Start ... react | ||||
Start ... reacti | ||||
Start results of rea | ||||
Rea Silvia | ||||
….. | ||||
readapt | ||||
End results of rea | ||||
Start results of react | ||||
react | ||||
….. | ||||
reactor | ||||
End results of react | ||||
Start ... reactiv | ||||
Start results of reacti | ||||
reaction | ||||
….. | ||||
reactive schizophrenia | ||||
Start ... reactive | ||||
reactivity | ||||
End results of reacti | ||||
Start results of reactive | ||||
reactive | ||||
reactive depression | ||||
reactive schizophrenia | ||||
End results of reactive | ||||
Start results of reactiv | ||||
reactivate | ||||
reactive | ||||
reactive depression | ||||
reactive schizophrenia | ||||
reactivity | ||||
End results of reactiv |
출력된 결과를 보기 쉽게 입력한 문자와 출력된 결과를 같은 컬럼에 배치했고, 아래의 도식화된 내용 처럼 서로 SelectMany로 연결되어 있다는 것을 확인 할 수 있다.
...마음에 드는 가?
"별로"
"뭐가 문젠가?"
"그냥 비동기 메소드에서 웹 서비스 호출하고, await로 기다리고, 도착하면 처리하는 것이랑 차이가 없는 거잖아?"
"결과적으로는 그렇치만,..음...음..."
"뭐? 왜그래?"
"너무 좋은데 딱히 표현할 방법이 없다!"
"컹...." 퍽퍽 퍼퍼벅
잠시 쉬어가는 의미로 다루지 않고 넘어갔던 부분에 대해 알아보고 처리하는 코드를 추가하자.
바로 웹 서비스의 오류 발생 상황이다. 웹 서비스의 특성상 네트워크의 문제가 발생하거나, 서비스 자체의 오류가 발생하면, 프로그램이 중지 되기 때문에, 반드시 오류 처리를 하고 넘어가야 한다. 또한, 지금 사용중인 서비스는 한 글자를 조회 요청하면, 서비스 오류를 발생 시키는데, 이 오류를 방지하기 위해 최소한 2글자 이상이 되도록 수정한다.
var input = (from evtPattern in
Observable.FromEventPattern<EventArgs>(txt,
"TextChanged")
select ((TextBox)
evtPattern.Sender).Text)
.Throttle(TimeSpan.FromSeconds(0.5))
.DistinctUntilChanged()
.Where(x =>
x.Length >= 3)
.Do(x =>
Debug.WriteLine("Start web service of " + x));
* Where 문이 저 곳이 아닌 다른 곳에 위치하면 약간 다른 결과가 나올 수 있다. 테스트 해보자
res.Subscribe(results =>
{
Debug.WriteLine("Start results of " + results.KeyWord);
foreach (var result in results.Result)
{
Debug.WriteLine(result.Word);
}
Debug.WriteLine("End results of " +
results.KeyWord);
}, ex => Debug.WriteLine("Exception
of web service : " + ex.Message));
출력결과가 정상적일 것이라고 생각한다. 다시 실행해서 결과 확인을 하는 것은 각자 하도록 하고, 이제 리스트박스에 결과를 출력 하도록 수정해 보자.
res.ObserveOn(SynchronizationContext.Current)
.Subscribe(results =>
{
lst.Items.Clear();
lst.Items.AddRange(results.Result.Select(p =>
p.Word).ToArray());
}, ex =>
Debug.WriteLine("Exception of web service : " + ex.Message));
실행 결과 아래와 같이 입력한 글자에 대한 유사 단어들의 목록을 리스트 박스에서 볼 수 있다.
휴~ 숨한번 돌리고, 이제 마지막 끝 판왕만 남았다.
방금 SelectMany를 이용해서 입력 값과 출력 값을 merge 시켜서 하나의 observable sequence를 만드는데 성공을 했다. 하지만, 여기에도 문제가 존재하는데....
rea -> react -> reacti -> reactiv -> reactive 순으로 입력을 진행하고 서비스를 호출했는데, 결과가 도착한 순서로 보자면 rea -> react -> reacti -> reactive -> reactiv 이다. 이렇게 도착 순서가 뒤바뀐 경우 리스트에 출력되는 내용은 어떻게 되는 것일까?
위의 일러스트에서 보듯이 reactive의 결과가 리스트에 출력이 된 후에 react가 도착이 되면, react의 결과가 리스트에 출력이 되기 때문에 오류 상황이 되는 것이다.
혹시 이러한 이슈를 해결하는 방법에 대해서 알고 있는 것이 있나? 아마도 일러스트에 나온 내용일 것이라고 생각한다.
일러스트의 내용으로 보면 react에 대한 웹 서비스 조회를 하는 중에 reactive를 다시 조회 할 때, 조회 중인 웹 서비스를 취소해서, 결과가 반환되지 못하도록 하는 것이다. 이렇게 처리하는 것을 "crossing out" 혹은 "muting"이라고 한다.
위에서 이야기한, 호출 중인 서비스를 취소하는 작업은 SelectMany의 특징 중 하나인 cancellation을 이용하면 쉽게 처리할 수 있다. 이 기능은 source가 웹 서비스를 호출 하고, 결과를 수신하기 전에, 새로운 키워드가 입력이 되어서 웹 서비스를 호출해야 한다면, 그 전에 존재하던 수신 대기 observable을 unsubscribe을 하게 된다. 마치 아래 일러스트 처럼 송유관에 벨브에 비유하기도 하는데, source는 웹 서비스 호출하는 부분이고, 그 뒤에 TakeUntil(value)가 붙어서 value에 값이 채워지기 전까지..라는 의미가 된다.
코드를 수정해 보자.
var res = (from inp in input
from words
in Service.MatchInDictAsync("wn", inp,
"prefix")
.ToObservable()
.Finally(() => Debug.WriteLine("Disposed request for "
+ inp))
.TakeUntil(input)
select new {KeyWord = inp,
Result = words});
Service.MatchInDictAsync 메소드는 observable sequence가 아니기 때문에 ToObservable() 메소드를 이용해서 observable로 변경해 주고, unsubscirbe가되는지 확인을 하기 위해 Finally를 추가하고, 마지막으로 TakeUntil을 사용한다. 실행 후 결과를 확인해 보자
Start web service of reacti
Start web service of
reacti
Start web service of
reactiv
Start web service of reactiv
Disposed
request for reacti
Start web service of
reactive
Disposed request for reactiv
Start web
service of reactive
Disposed request for reactive
Start
results of reactive
reactive
reactive depression
reactive
schizophrenia
End results of reactive
Debug창에 출력된 log로, 새로운 단어로 웹 서비스를 호출하기 전에 호출 중인 웹 서비스를 unsubscribe하는 것을 확인 할 수 있다. 그런데, 약간 눈에 거슬리는 것이 있는데, 동일한 문장이 보이는 것이다. 중복 문장은 Do로 출력한 Start web service of xxx라는 내용인데, 그 이유는, SelectMany와 TakeUntil 2개의 Subscribe이 존재하기 때문이다.
* pdf에 Switch에 대한 사항은 소스에서 Switch를 사용할 수 없어서 일단 제외 한다. Switch가 제거 된 것은 아닌데, 사용하지 못하는 이유는 찾게되면 다시 포스트를 작성하도록 하겠다.
지금까지 part2~part5까지 4회에 걸쳐서 Rx HOL .NET.pdf의 내용을 Visua Studio 2013와 Rx 2.2.x 버전으로 구현하고, 설명하였다. 이제 Rx에 대한 사용법에 대해서 약간은 느낌이 왔을 것이라고 생각하고, 다음에도 더 좋은 자료가 있으면 포스트를 하도록 하겠다.
이제 part5 포스트가 일주일이나 걸린 이유를 공개한다. 짜잔~
*********************************************************************************
pdf에 나온 것과 같은 서비스를 이용해서 작업을 진행할려고 했는데, 갑자기 서비스에 문제가 발생한 것을 알게되었다. 그래서, 부득이하게 다른 예를 이용하는 포스트를 작성 한다. 순식간에 내용이 끊어지고 다른 형태로 한다고 만이 놀랐을 것 같다. 나도 셈플 프로젝트를 만드는데, 서비스 오류가 발생해서, 내가 뭘 잘 못했는 줄 알고, 이것 저것 찾아본다고 고생했다. 혹시라도, 포스트 작성 중 서비스가 다시 정상으로 돌아오면 그 때 계속하도록 하겠다.
9월 18일 오후 정말 파란 만장하다고 해야하나;; 새로운 프로젝트 만들어서 열심히 이것 저것 해보고 있었는데 지금 서비스가 정상화 되었다. 하하하..그렇다면 다시 pdf내용으로 작성한다.
*********************************************************************************
2. 새로운 프로젝트로..
pdf를 따라하지 못하니 새로운 유니버셜 프로젝트를 만들어서 작업을 진행하도록 하자.
포스트 작성 중에 한통의 메일을 확인 했다. 두달전에 물에 빠진 루미아 925를 홍콩으로 a/s를 보냈었는데, 회생 불능 판정을 받았다는 내용이였다. 모든 기능은 정상적으로 동작하는 듯 했는데..(컴퓨터와 연결하면 동기화까지도 했었는데..) lcd에 아무것도 표시가 되지 않는 것으로 보아서, lcd만 갈아 끼우면 될 것 같았는데... 앞으로 윈폰8을 언제 다시 만질 수 있을지..그래서, 이번에는 루미아 925를 추모(?)하는 마음에서 윈폰에서 작업을 하도록 하겠다.
새로운 Blank 유니버셜앱 프로젝트를 생성 후 Nuget package에서 rx를 선택해서 Windows store app, Windows Phone app 2개의 프로젝트에 모두 추가하도록 한다.
윈도우 폰 프로젝트를 선택 후 Set as Start up Project를 선택해서 시작 프로젝트로 만들고, MainPage.xaml에 다음과 같이 코딩한다.
2.1 MainPage.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition
Height="70"/>
<RowDefinition
Height="60"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<TextBlock Text="Rx part5"
FontSize="80"/>
<StackPanel Grid.Row="1"
Orientation="Horizontal">
<TextBlock Text="Search text :"
FontSize="30" VerticalAlignment="Center"/>
<TextBox
x:Name="tb" MinWidth="100"/>
</StackPanel>
<WebView x:Name="wv" Grid.Row="2" Source="http://kaki104.tistory.com/m"/>
</Grid>
실행 결과
2.2 MainPage.xaml.cs
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
tb.Text = string.Empty;
var input = (from evtPattern in
Observable.FromEventPattern<TextChangedEventArgs>(tb,
"TextChanged")
select
((TextBox)evtPattern.Sender).Text)
.Throttle(TimeSpan.FromSeconds(1))
.DistinctUntilChanged();
input.Subscribe(
txt =>
Debug.WriteLine("Input text is {0}", txt));
}
part4에서 사용했던 TextChanged 이벤트를 이용하는 부분을 복사해서 붙여 넣은 후 입력 문자를 debug 창에 출력되도록 수정하였다. 추가로 EventArgs도 TextChangedEventArgs로 수정 했다.
Input text is react
Input text is reactive
정상적으로 결과가 되는 것을 확인했다.
이제 검색어를 입력하면 wikipedia에서 검색을 하여 결과를 화면에 출력하도록 소스를 수정해보자.
input.ObserveOn(SynchronizationContext.Current)
.Subscribe(
inputText =>
{
Debug.WriteLine("Search text : {0}",
inputText);
wv.Navigate(new Uri("http://en.wikipedia.org/wiki/"
+ inputText));
});
실행 후 reactive를 입력하면 아래와 같은 결과가 표시 된다.
.ObserveOn(SynchronizationContext.Current)
part4에서 ObserveOn에 사용되는 IScheduler가 조금씩 다르다고 했는데, 이전에 사용하던 스케줄러 중에 몇가지가 사용을 할 수 없도록 삭제되어 있었다. 그래서 정확하게 어떤 스케줄러를 사용해야하는지 찾아보았지만, 검색에 실패! 결국 part4에서 사용한 것을 그대로 사용하기로 했다. 추후에 더 정확한 스케줄러를 찾게 되면 수정 하도록 하겠다.
어떻게 생각하면 각 플랫폼마다 여러가지 스케줄러를 혼용함으로 인해 발생하는 복잡함을 감소하기 위해서 SynchronizationContext.Current로 대동 단결 하도록 만들었는지도 모르겠다. 과거 버전에 있던 여러가지 스케줄러를 왜 삭제했는지는 히스토리를 찾아보거나, Rx 팀에 문의를 해보아야 정확하게 알 수 있을 것 같다.
...
"음? 왜 갑자기 끝난거야?"
"너무 길면, 읽기 싫어지고, 손가락이 아프다."
"힘들어서가 아니고?"
"험험..그럼 이만.." =3 =3 =3
9. End
여기까지가 웹 서비스가 고장이난 기간 동안 작성한 내용이다. 정신 없기는 하지만, 2개의 내용 모두를 그대로 포함하기로 하고, 소스도 모두 포함한다.
콘솔작업 프로젝 소스
유니버셜 프로젝 소스
|