Reactive Extensionsで非同期処理を簡単に

19,040 views
18,802 views

Published on

すまべん関東 Rx資料

Published in: Technology
0 Comments
27 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
19,040
On SlideShare
0
From Embeds
0
Number of Embeds
5,482
Actions
Shares
0
Downloads
86
Comments
0
Likes
27
Embeds 0
No embeds

No notes for slide

Reactive Extensionsで非同期処理を簡単に

  1. 1. Reactive ExtensionsでWP7の非同期処理を簡単に @neuecc – 2011/5/21
  2. 2. Profile Twitter => @neuecc Blog => http://neue.cc/ HNはneuecc 読むときは“のいえ”と読ませてます  ドメイン繋いだだけなので発音するの考えてなかっ た(のでccは抜きで←発音しにくいですから) Microsoft MVP for Visual C#(2011/4-) WP7で作った物  ReactiveOAuth, Utakotoha WP7の好きなtheme  light + lime
  3. 3. Agenda LINQの概要/LINQとしてのReactive Extensions 非同期処理の面倒さと如何にRxが癒すか .NETにおける非同期パターンの説明 Rxの基本(購読, キャンセル, 例外処理) 非同期処理で使うRxのメソッド概略 作った物紹介
  4. 4. Linq to Introduction
  5. 5. Language INtegrated Query// クエリ構文var query = from x in source where x % 2 == 0 select x * x;// メソッド構文var query = source .Where(x => x % 2 == 0) .Select(x => x * x);
  6. 6. LINQって何? データソースを統一的な書法で処理できる WhereでフィルタしてSelectで射影できるならそれ はLINQって言えます! SQL関係ないし、C#も飛び越えて生きる概念 JavaScript移植もあるしね  linq.js – http://linqjs.codeplex.com/  RxJS(Reactive Extensions for JavaScript)
  7. 7. LINQのデータソースとはto Objects to Xml to Sql 配列 XML Database List<T> (JSON) Stream 無限リストto Events to Asynchronous TextChanged IO – WebRequest ジェスチャー Timer – ポーリング センサー Thread – 長時間かかる処理 MusicPlayer Reactive Extensions
  8. 8. Reactive Extensions = Linq to Events Linq to AsynchronousLINQにおけるデータソースの拡張……というだけじゃない時間という軸を中心にした基盤
  9. 9. Rxの基本軸は時間IE<T> length IE<T> async eventIO<T> time=> IE<T>も乗っかることで「全てのデータソース」が合成可能に!
  10. 10. Async Programming Blues
  11. 11. 古き良き同期コードvar req = WebRequest.Create("http://hoge/");var res = req.GetResponse();var str = new StreamReader(res.GetResponseStream()).ReadToEnd(); 簡単。でも、Silverlight/WP7には同期APIは無い。  UIがブロックされるのダメ絶対 Thread立ててそっちで実行させれば?  まあそうです  でもないものはないのでしょうがない  そのかわり特に気を使わなくても必ずUIノンブロッ キングになる(※但しCPUヘヴィな処理は除く)
  12. 12. しょうがないので非同期で書くvar req = WebRequest.Create("http://hoge");req.BeginGetResponse(ar =>{ var res = req.EndGetResponse(ar); var str = new StreamReader(res.GetResponseStream()) .ReadToEnd(); Dispatcher.BeginInvoke(() => MessageBox.Show(str));}, null); -> EndHoge 非同期はBeginHoge 基本、クロージャ全開で書く 面倒くさいけれど、まあこれぐらいなら?
  13. 13. ネストするとかなりヤバいvar req = WebRequest.Create("http://hoge");req.BeginGetResponse(ar =>{ var res = req.EndGetResponse(ar); var url = new StreamReader(res.GetResponseStream()) .ReadToEnd(); var req2 = WebRequest.Create(url); req2.BeginGetResponse(ar2 => { var res2 = req2.EndGetResponse(ar2); var str = new StreamReader(res2.GetResponseStream()) .ReadToEnd(); Dispatcher.BeginInvoke(() => MessageBox.Show(str)); }, null);}, null);
  14. 14. 通信箇所に例外処理は必須 WP7ではネットワーク周りのコードでは100%例外 発生の可能性がある 圏外だったり通信が超低速だったりすると?  Hello, Timeout.  山崎春のWebException祭り  何の手立てもしないとアプリ落ちるよ 予期される例外だし、固有の後処理もあるだろう し、復帰可能にすべきなので、その場その場で catchして始末するのが無難
  15. 15. 内側なのは見た目だけvar req = WebRequest.Create("http://hoge");req.BeginGetResponse(ar =>{ 非同期中に起こる例外はEnd時に戻される try { var res = req.EndGetResponse(ar); var url = new StreamReader(res.GetResponseStream()).ReadToEnd(); var req2 = WebRequest.Create(url); req2.BeginGetResponse(ar2 => ここの例外をcatchしてない { var res2 = req2.EndGetResponse(ar2); var str = new StreamReader(res2.GetResponseStream()).ReadToEnd(); Dispatcher.BeginInvoke(() => textBlock1.Text = str); }, null); } catchできるのは同じ関数のブロック内だけ catch(WebException e) { Dispatcher.BeginInvoke(() => MessageBox.Show(e.ToString())); }}, null);
  16. 16. もはやカオスすぎて頭痛いvar req = WebRequest.Create("http://hoge");req.BeginGetResponse(ar =>{ try { var res = req.EndGetResponse(ar); var url = new StreamReader(res.GetResponseStream()).ReadToEnd(); var req2 = WebRequest.Create(url); req2.BeginGetResponse(ar2 => { try { var res2 = req2.EndGetResponse(ar2); var str = new StreamReader(res2.GetResponseStream()).ReadToEnd(); Dispatcher.BeginInvoke(() => MessageBox.Show(str)); } catch (WebException e) { Dispatcher.BeginInvoke(() => MessageBox.Show(e.ToString())); } }, null); } catch (WebException e) { Dispatcher.BeginInvoke(() => MessageBox.Show(e.ToString())); }}, null);
  17. 17. Reactive Extensionsを使うとネストが消滅し完全フラット 拡張メソッド(後で説明します) WebRequest.Create("http://hoge") .GetResponseAsObservable() .Select(res => new StreamReader(res.GetResponseStream()).ReadToEnd()) .SelectMany(s => WebRequest.Create(s).GetResponseAsObservable()) .Select(res => new StreamReader(res.GetResponseStream()).ReadToEnd()) .ObserveOnDispatcher() .Subscribe( s => MessageBox.Show(s), e => MessageBox.Show(e.ToString())); 内部で発生する例外は全てここで扱える
  18. 18. Rxを使うことの利点 ネストがなくなって平らに 統一的な例外処理が可能+ その他の機能もいっぱい  リトライ処理  イベントやシーケンスとの合成など
  19. 19. Asynchronous Patterns
  20. 20. 非同期のもと 非同期パターンは概ね二つ  APM(Asynchronous Programming Model)  BeginXxx-EndXxx  WebRequest.BeginGetResponseとか  EAP(Event-based Asynchronous Pattern)  XxxAsync-XxxCompleted  WebClient.DownloadStringAsync/Copletedとか 将来的には?  Rx(WP7では標準搭載ですが.NET4ではまだ)  Task(.NET4では標準搭載ですがWP7ではまだ)  C# 5.0 Async(まだCTP, 恐らく2年ぐらい先)
  21. 21. どっちがいいの? Rxで使うならAPMのほうが相性良い APMは上から下まで流れてるが、EAPは最後に発火 させなければならない これはネストする場合に致命的に面倒// APMWebRequest.Create("http://hoge") .GetResponseAsObservable() .Subscribe();// EAP Subscribe後に発火var wc = new WebClient();wc.DownloadStringCompletedAsObservable() .Subscribe();wc.DownloadStringAsync("http://hoge");
  22. 22. 拡張メソッドのすゝめ APM,WebRequest->WebResponseはプリミティブ すぎて、一々Stream扱ったり面倒くさい WebClientのDownloadString的なのが欲しい なら拡張メソッドで自作すれば解決 FromEventやFromAsyncPatternは定型句なので、こ ちらも拡張メソッドで隔離するのがお薦め
  23. 23. FromEvent(FromEventPattern) 戻り値はIEvent<EventArgs>のIO<T>public staticIObservable<IEvent<DownloadStringCompletedEventArgs>> DownloadStringCompletedAsObservable(this WebClient webClient){ return Observable.FromEvent< DownloadStringCompletedEventHandler, DownloadStringCompletedEventArgs>( h => h.Invoke, // おまじない h => webClient.DownloadStringCompleted += h, h => webClient.DownloadStringCompleted -= h);} FromEvent<EventHandler,EventArgs>
  24. 24. FromAsyncPattern FromAsyncPatternの戻り値はデリゲート  つまり自分でInvokeするまで実行されない 拡張メソッドにするなら即実行のほうが便利? Task.Factory.StartNew的なイメージでpublic static IObservable<WebResponse> GetResponseAsObservable(this WebRequest request){ return Observable.FromAsyncPattern<WebResponse>( request.BeginGetResponse, request.EndGetResponse) .Invoke();}
  25. 25. DownloadStringAsync(の自作) Stringが戻ったほうが便利ですよね POSTなども同じように作っておくと楽になるpublic static IObservable<string> DownloadStringAsync(this WebRequest request){ return request.GetResponseAsObservable() .Select(res => { using (var stream = res.GetResponseStream()) using (var sr = new StreamReader(stream)) { return sr.ReadToEnd(); } });}
  26. 26. 最初の例もこんなにスッキリ Rxが提供してくれているのは基本的な道具 Rxは「分離しやすい」のも特徴的なメリット <T>への拡張メソッドや、IO<T>->IO<T>の拡張メ ソッドなどを作って、すっきりさせようWebRequest.Create("http://hoge") .DownloadStringAsync() .SelectMany(s => WebRequest.Create(s).DownloadStringAsync()) .ObserveOnDispatcher() .Subscribe( s => MessageBox.Show(s), e => MessageBox.Show(e.ToString()));
  27. 27. Basics of Rx
  28. 28. 書き方の基本 System.Observableを参照する Microsoft.Phone.Reactiveを参照する  WP7には標準搭載  NET4版は別途インストールしてSystem.Reactiveを 定形的な流れは FromEvent/FromAsyncして(IO<T>の生成) SelectなりWhereなりLINQでデータを加工して ObserveOnDispatcherして(値をUIスレッドに戻す) Subscribeする
  29. 29. ところでキャンセルしたい// Subscribeの戻り値はIDisposableなので、Disposeすればおkvar disposable = AsyncMethod().Subscribe();disposable.Dispose();// イベントの場合はデタッチになるよvar buttonClick = button.ClickAsObservable().Subscribe();buttonClick.Dispose();// Listに入れてまとめてDisposeとかvar disposables = new List<IDisposable>();disposable.Add(button.ClickAsObservable().Subscribe());disposable.Add(button.ClickAsObservable().Subscribe());disposable.ForEach(d => d.Dispose());// IList<IDisposable>はCompositeDisposableというのもあるvar disposables = new CompositeDisposable();disposable.Dispose();
  30. 30. 例外処理は? 何も書かない  例外はcatchせずスローされてくる SubscribeのonErrorに書く  Rxのチェーンで発生した例外を全てcatchする  ここに空のものを書けば例外無視が成立とかも  ※Tips:一部メソッドが間に挟まれていると(Publishなど) 場合によってはcatchされなくなることも(Publishは内部 で自前でSubscribeしているためチェーンが途切れてる) Catchメソッドを使う  例外の型を指定した通常のtry-catchに近いもの  戻り値にEmptyを返せば終了、何か別の値を返せばそ れを代替として流すということになる
  31. 31. 基本的なのはこの3つ// 何も書かないので例外がスローされてくるWebRequest.Create("http://hoge") .DownloadStringAsync() .Subscribe(Debug.WriteLine);// SubscribeのonErrorで全てcatchWebRequest.Create("http://hoge") .DownloadStringAsync() .Subscribe(Debug.WriteLine, e => { });// CatchでWebExceptionだけcatchWebRequest.Create("http://hoge") .DownloadStringAsync() .Catch((WebException e) => Observable.Empty<string>()) .Subscribe(Debug.WriteLine);
  32. 32. Compose Patterns
  33. 33. SelectMany - 直列の結合 AsyncA AsyncBZip - 並列の結合 AsyncA Result AsyncB
  34. 34. SelectMany + Zip - 合成の例 AsyncA AsyncB Result AsyncC AsyncA().SelectMany(a => AsyncB(a)) .Zip(AsyncC(), (b, c) => new { b, c });
  35. 35. ForkJoin - 並列同時実行 AsyncA AsyncB Result AsyncC AsyncDObservable.ForkJoin(AsyncA(), AsyncB(), AsyncC(), AsyncD()) .Select(xs => new { a=xs[0], b=xs[1], c=xs[2], d=xs[3] });
  36. 36. 多重from(SelectMany) AsyncA AsyncB AsyncC Result var asyncQuery = from a in AsyncA() from b in AsyncB(a) from c in AsyncC(a, b) select new { a, b, c };
  37. 37. Conclusion
  38. 38. まとめ FromEvent/Asyncは拡張メソッドで隔離 ついでにReadToEndなんかも隔離 とりあえずSubscribeの前にObserveOnDispatcher onErrorとCatchで例外をコントロール 色々な合成メソッドで流れをコントロール もう非同期なんて怖くない! Rxで書くと実は同期で書くよりも柔軟 むしろもう同期でなんて書きたくない!
  39. 39. Real World Rx
  40. 40. ReactiveOAuth http://reactiveoauth.codeplex.com/ TwitterとかのOAuth認証/通信用ライブラリ 以下のWP7アプリで使われています!  SongTweeter @iseebi  NumberPush @mitsuba_tan  Utakata TextPad @kaorun  HirganaTwit @hatsune_
  41. 41. どんな時に使えるの? Twitterクライアントを作るわけではないけれど、 ステータスだけTwitterに投稿したい、などはよく あること(特に最近は何でもTwitterだし)new OAuthClient(ConsumerKey, ConsumerSecret, accessToken){ MethodType = MethodType.Post, Url = "http://api.twitter.com/1/statuses/update.xml", Parameters = { { "status", "ここに投稿する文章" } }}.GetResponseText() // 投稿して、戻り値を得る.Select(XElement.Parse) // xmlの場合はパースしたいよね.Subscribe(); // なにか処理するなり例外処理入れるなり
  42. 42. ポータビリティ WP7だけじゃなく.NET4/SL4向けもあり コードの99%をWP7と共有している  残り1%は最近の更新で.NET版Rxが一部WP7版と互換 なくなったため Rxをベースに置くと、コードの可搬性が圧倒的に 高まる 機能面でもRxにタダ乗り出来るので(エラー・リト ライ・タイムアウトなど全部Rxにおまかせ) ライブラリ本体はシンプルなコードのままで強力 な機能を持てる
  43. 43. Utakotoha http://utakotoha.codeplex.com/ 再生中の曲に応じて日本語歌詞を表示する マーケットプレイスで公開中(Free) ソースコードも公開中
  44. 44. コードの中身 コードは無理やり全部Rxで割と実験的 GUI周りは強引でボロボロで酷い イベント周りなどは面白く仕上がったかも Modelを別に立てた.NET4クラスライブラリにリン クで参照することによりMSTestでユニットテスト Molesというモックライブラリを使ってWP7のイベ ント自体を乗っ取り(音楽再生の情報をテストのた めに任意に生成したり) 上手くいってるかはノーコメント
  45. 45. Deep Dive Rx
  46. 46. 公式見るのがいいよやっぱり Data Developer Center - Rx http://msdn.microsoft.com/en-us/data/gg577609 二つのドキュメントが出ています  Hands-on-Lab  チュートリアル式で触りながら分かりやすく  まず見て欲しい  Design Guidelines  マニュアルみたいなもの  更に深くへ
  47. 47. 日本語情報は? ねぇよんなもん うちのサイトでも見てください:)  http://neue.cc/  http://neue.cc/category/rx

×