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

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

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

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 ⚫ マニュアルみたいなもの ⚫ 更に深くへ 公式見るのがいいよやっぱり