Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

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

Avatar for Yoshifumi Kawai

Yoshifumi Kawai

May 21, 2011
Tweet

More Decks by Yoshifumi Kawai

Other Decks in Technology

Transcript

  1.  Twitter => @neuecc  Blog => http://neue.cc/  HNはneuecc

    読むときは“のいえ”と読ませてます ⚫ ドメイン繋いだだけなので発音するの考えてなかっ た(のでccは抜きで←発音しにくいですから)  Microsoft MVP for Visual C#(2011/4-)  WP7で作った物 ⚫ ReactiveOAuth, Utakotoha  WP7の好きなtheme ⚫ light + lime Profile
  2. // クエリ構文 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); Language INtegrated Query
  3. Reactive Extensions LINQのデータソースとは to Objects 配列 List<T> Stream 無限リスト to

    Xml XML (JSON) to Sql Database to Events TextChanged ジェスチャー センサー MusicPlayer to Asynchronous IO – WebRequest Timer – ポーリング Thread – 長時間かかる処理
  4. Reactive Extensions = Linq to Events Linq to Asynchronous LINQにおけるデータソースの拡張

    ……というだけじゃない 時間という軸を中心にした基盤
  5. var req = WebRequest.Create("http://hoge/"); var res = req.GetResponse(); var str

    = new StreamReader(res.GetResponseStream()).ReadToEnd();  簡単。でも、Silverlight/WP7には同期APIは無い。 ⚫ UIがブロックされるのダメ絶対  Thread立ててそっちで実行させれば? ⚫ まあそうです ⚫ でもないものはないのでしょうがない ⚫ そのかわり特に気を使わなくても必ずUIノンブロッ キングになる(※但しCPUヘヴィな処理は除く) 古き良き同期コード
  6. 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);  非同期はBeginHoge -> EndHoge  基本、クロージャ全開で書く  面倒くさいけれど、まあこれぐらいなら? しょうがないので非同期で書く
  7. 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); ネストするとかなりヤバい
  8.  WP7ではネットワーク周りのコードでは100%例外 発生の可能性がある  圏外だったり通信が超低速だったりすると? ⚫ Hello, Timeout. ⚫ 山崎春のWebException祭り

    ⚫ 何の手立てもしないとアプリ落ちるよ  予期される例外だし、固有の後処理もあるだろう し、復帰可能にすべきなので、その場その場で catchして始末するのが無難 通信箇所に例外処理は必須
  9. 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 => { var res2 = req2.EndGetResponse(ar2); var str = new StreamReader(res2.GetResponseStream()).ReadToEnd(); Dispatcher.BeginInvoke(() => textBlock1.Text = str); }, null); } catch(WebException e) { Dispatcher.BeginInvoke(() => MessageBox.Show(e.ToString())); } }, null); 内側なのは見た目だけ 非同期中に起こる例外はEnd時に戻される ここの例外をcatchしてない catchできるのは同じ関数のブロック内だけ
  10. 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); もはやカオスすぎて頭痛い
  11. 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())); Reactive Extensionsを使うと 内部で発生する例外は全てここで扱える ネストが消滅し完全フラット 拡張メソッド(後で説明します)
  12.  非同期パターンは概ね二つ ⚫ 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年ぐらい先) 非同期のもと
  13.  Rxで使うならAPMのほうが相性良い  APMは上から下まで流れてるが、EAPは最後に発火 させなければならない  これはネストする場合に致命的に面倒 // APM WebRequest.Create("http://hoge")

    .GetResponseAsObservable() .Subscribe(); // EAP var wc = new WebClient(); wc.DownloadStringCompletedAsObservable() .Subscribe(); wc.DownloadStringAsync("http://hoge"); どっちがいいの? Subscribe後に発火
  14. public static IObservable<IEvent<DownloadStringCompletedEventArgs>> DownloadStringCompletedAsObservable(this WebClient webClient) { return Observable.FromEvent< DownloadStringCompletedEventHandler,

    DownloadStringCompletedEventArgs>( h => h.Invoke, // おまじない h => webClient.DownloadStringCompleted += h, h => webClient.DownloadStringCompleted -= h); } FromEvent(FromEventPattern) 戻り値はIEvent<EventArgs>のIO<T> FromEvent<EventHandler,EventArgs>
  15.  FromAsyncPatternの戻り値はデリゲート ⚫ つまり自分でInvokeするまで実行されない  拡張メソッドにするなら即実行のほうが便利?  Task.Factory.StartNew的なイメージで public static

    IObservable<WebResponse> GetResponseAsObservable(this WebRequest request) { return Observable.FromAsyncPattern<WebResponse>( request.BeginGetResponse, request.EndGetResponse) .Invoke(); } FromAsyncPattern
  16.  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(); } }); } DownloadStringAsync(の自作)
  17.  Microsoft.Phone.Reactiveを参照する  System.Observableを参照する ⚫ WP7には標準搭載 ⚫ NET4版は別途インストールしてSystem.Reactiveを  定形的な流れは

    FromEvent/FromAsyncして(IO<T>の生成) SelectなりWhereなりLINQでデータを加工して ObserveOnDispatcherして(値をUIスレッドに戻す) Subscribeする 書き方の基本
  18. // Subscribeの戻り値はIDisposableなので、Disposeすればおk var 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(); ところでキャンセルしたい
  19.  何も書かない ⚫ 例外はcatchせずスローされてくる  SubscribeのonErrorに書く ⚫ Rxのチェーンで発生した例外を全てcatchする ⚫ ここに空のものを書けば例外無視が成立とかも

    ⚫ ※Tips:一部メソッドが間に挟まれていると(Publishなど) 場合によってはcatchされなくなることも(Publishは内部 で自前でSubscribeしているためチェーンが途切れてる)  Catchメソッドを使う ⚫ 例外の型を指定した通常のtry-catchに近いもの ⚫ 戻り値にEmptyを返せば終了、何か別の値を返せばそ れを代替として流すということになる 例外処理は?
  20. // 何も書かないので例外がスローされてくる WebRequest.Create("http://hoge") .DownloadStringAsync() .Subscribe(Debug.WriteLine); // SubscribeのonErrorで全てcatch WebRequest.Create("http://hoge") .DownloadStringAsync() .Subscribe(Debug.WriteLine,

    e => { }); // CatchでWebExceptionだけcatch WebRequest.Create("http://hoge") .DownloadStringAsync() .Catch((WebException e) => Observable.Empty<string>()) .Subscribe(Debug.WriteLine); 基本的なのはこの3つ
  21. AsyncA AsyncC AsyncD Result Observable.ForkJoin(AsyncA(), AsyncB(), AsyncC(), AsyncD()) .Select(xs =>

    new { a=xs[0], b=xs[1], c=xs[2], d=xs[3] }); ForkJoin - 並列同時実行 AsyncB
  22. var asyncQuery = from a in AsyncA() from b in

    AsyncB(a) from c in AsyncC(a, b) select new { a, b, c }; 多重from(SelectMany) AsyncA AsyncB AsyncC Result
  23.  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(); // なにか処理するなり例外処理入れるなり どんな時に使えるの?
  24.  WP7だけじゃなく.NET4/SL4向けもあり  コードの99%をWP7と共有している ⚫ 残り1%は最近の更新で.NET版Rxが一部WP7版と互換 なくなったため  Rxをベースに置くと、コードの可搬性が圧倒的に 高まる

     機能面でもRxにタダ乗り出来るので(エラー・リト ライ・タイムアウトなど全部Rxにおまかせ)  ライブラリ本体はシンプルなコードのままで強力 な機能を持てる ポータビリティ
  25.  コードは無理やり全部Rxで割と実験的  GUI周りは強引でボロボロで酷い  イベント周りなどは面白く仕上がったかも  Modelを別に立てた.NET4クラスライブラリにリン クで参照することによりMSTestでユニットテスト 

    Molesというモックライブラリを使ってWP7のイベ ント自体を乗っ取り(音楽再生の情報をテストのた めに任意に生成したり)  上手くいってるかはノーコメント コードの中身
  26.  Data Developer Center - Rx  http://msdn.microsoft.com/en-us/data/gg577609  二つのドキュメントが出ています

    ⚫ Hands-on-Lab ⚫ チュートリアル式で触りながら分かりやすく ⚫ まず見て欲しい ⚫ Design Guidelines ⚫ マニュアルみたいなもの ⚫ 更に深くへ 公式見るのがいいよやっぱり