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

47
Reactive ExtensionsWP7の非同期処理を簡単に @neuecc – 2011/5/21

description

すまべん関東 Rx資料

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

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

Reactive Extensionsで

WP7の非同期処理を簡単に

@neuecc – 2011/5/21

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

Twitter => @neuecc

Blog => http://neue.cc/

HNはneuecc 読むときは“のいえ”と読ませてます

ドメイン繋いだだけなので発音するの考えてなかった(のでccは抜きで←発音しにくいですから)

Microsoft MVP for Visual C#(2011/4-)

WP7で作った物

ReactiveOAuth, Utakotoha

WP7の好きなtheme

light + lime

Profile

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

LINQの概要/LINQとしてのReactive Extensions

非同期処理の面倒さと如何にRxが癒すか

.NETにおける非同期パターンの説明

Rxの基本(購読, キャンセル, 例外処理)

非同期処理で使うRxのメソッド概略

作った物紹介

Agenda

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

Linq to Introduction

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

// クエリ構文

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

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

データソースを統一的な書法で処理できる

WhereでフィルタしてSelectで射影できるならそれはLINQって言えます!

SQL関係ないし、C#も飛び越えて生きる概念

JavaScript移植もあるしね

linq.js – http://linqjs.codeplex.com/

RxJS(Reactive Extensions for JavaScript)

LINQって何?

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

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 – 長時間かかる処理

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

Reactive Extensions =

Linq to Events

Linq to Asynchronous LINQにおけるデータソースの拡張

……というだけじゃない

時間という軸を中心にした基盤

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

Rxの基本軸は時間

IE<T> length

IO<T> time

event

async

IE<T>

=> IE<T>も乗っかることで「全てのデータソース」が合成可能に!

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

Async Programming Blues

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

var req = WebRequest.Create("http://hoge/");

var res = req.GetResponse();

var str = new StreamReader(res.GetResponseStream()).ReadToEnd();

簡単。でも、Silverlight/WP7には同期APIは無い。

UIがブロックされるのダメ絶対

Thread立ててそっちで実行させれば?

まあそうです

でもないものはないのでしょうがない

そのかわり特に気を使わなくても必ずUIノンブロッキングになる(※但しCPUヘヴィな処理は除く)

古き良き同期コード

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

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

基本、クロージャ全開で書く

面倒くさいけれど、まあこれぐらいなら?

しょうがないので非同期で書く

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

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);

ネストするとかなりヤバい

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

WP7ではネットワーク周りのコードでは100%例外発生の可能性がある

圏外だったり通信が超低速だったりすると?

Hello, Timeout.

山崎春のWebException祭り

何の手立てもしないとアプリ落ちるよ

予期される例外だし、固有の後処理もあるだろうし、復帰可能にすべきなので、その場その場でcatchして始末するのが無難

通信箇所に例外処理は必須

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

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できるのは同じ関数のブロック内だけ

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

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);

もはやカオスすぎて頭痛い

Page 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()));

Reactive Extensionsを使うと

内部で発生する例外は全てここで扱える

ネストが消滅し完全フラット 拡張メソッド(後で説明します)

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

ネストがなくなって平らに

統一的な例外処理が可能

+ その他の機能もいっぱい

リトライ処理

イベントやシーケンスとの合成など

Rxを使うことの利点

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

Asynchronous Patterns

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

非同期パターンは概ね二つ

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年ぐらい先)

非同期のもと

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

Rxで使うならAPMのほうが相性良い

APMは上から下まで流れてるが、EAPは最後に発火させなければならない

これはネストする場合に致命的に面倒

// APM

WebRequest.Create("http://hoge")

.GetResponseAsObservable()

.Subscribe();

// EAP

var wc = new WebClient();

wc.DownloadStringCompletedAsObservable()

.Subscribe();

wc.DownloadStringAsync("http://hoge");

どっちがいいの?

Subscribe後に発火

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

APM, WebRequest->WebResponseはプリミティブすぎて、一々Stream扱ったり面倒くさい

WebClientのDownloadString的なのが欲しい

なら拡張メソッドで自作すれば解決

FromEventやFromAsyncPatternは定型句なので、こちらも拡張メソッドで隔離するのがお薦め

拡張メソッドのすゝめ

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

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>

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

FromAsyncPatternの戻り値はデリゲート

つまり自分でInvokeするまで実行されない

拡張メソッドにするなら即実行のほうが便利?

Task.Factory.StartNew的なイメージで

public static IObservable<WebResponse>

GetResponseAsObservable(this WebRequest request)

{

return Observable.FromAsyncPattern<WebResponse>(

request.BeginGetResponse, request.EndGetResponse)

.Invoke();

}

FromAsyncPattern

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

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(の自作)

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

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()));

最初の例もこんなにスッキリ

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

Basics of Rx

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

System.Observableを参照する

Microsoft.Phone.Reactiveを参照する

WP7には標準搭載

NET4版は別途インストールしてSystem.Reactiveを

定形的な流れは

FromEvent/FromAsyncして(IO<T>の生成)

SelectなりWhereなりLINQでデータを加工して

ObserveOnDispatcherして(値をUIスレッドに戻す)

Subscribeする

書き方の基本

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

// 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();

ところでキャンセルしたい

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

何も書かない

例外はcatchせずスローされてくる

SubscribeのonErrorに書く

Rxのチェーンで発生した例外を全てcatchする

ここに空のものを書けば例外無視が成立とかも

※Tips:一部メソッドが間に挟まれていると(Publishなど)

場合によってはcatchされなくなることも(Publishは内部で自前でSubscribeしているためチェーンが途切れてる)

Catchメソッドを使う

例外の型を指定した通常のtry-catchに近いもの

戻り値にEmptyを返せば終了、何か別の値を返せばそれを代替として流すということになる

例外処理は?

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

// 何も書かないので例外がスローされてくる

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つ

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

Compose Patterns

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

AsyncA AsyncB

SelectMany - 直列の結合

AsyncA

Zip - 並列の結合

AsyncB

Result

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

AsyncA AsyncB

AsyncC

Result

AsyncA().SelectMany(a => AsyncB(a)) .Zip(AsyncC(), (b, c) => new { b, c });

SelectMany + Zip - 合成の例

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

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

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

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

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

Conclusion

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

FromEvent/Asyncは拡張メソッドで隔離

ついでにReadToEndなんかも隔離

とりあえずSubscribeの前にObserveOnDispatcher

onErrorとCatchで例外をコントロール

色々な合成メソッドで流れをコントロール

もう非同期なんて怖くない!

Rxで書くと実は同期で書くよりも柔軟

むしろもう同期でなんて書きたくない!

まとめ

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

Real World Rx

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

http://reactiveoauth.codeplex.com/

TwitterとかのOAuth認証/通信用ライブラリ

以下のWP7アプリで使われています!

SongTweeter @iseebi

NumberPush @mitsuba_tan

Utakata TextPad @kaorun

HirganaTwit @hatsune_

ReactiveOAuth

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

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(); // なにか処理するなり例外処理入れるなり

どんな時に使えるの?

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

WP7だけじゃなく.NET4/SL4向けもあり

コードの99%をWP7と共有している

残り1%は最近の更新で.NET版Rxが一部WP7版と互換なくなったため

Rxをベースに置くと、コードの可搬性が圧倒的に高まる

機能面でもRxにタダ乗り出来るので(エラー・リトライ・タイムアウトなど全部Rxにおまかせ)

ライブラリ本体はシンプルなコードのままで強力な機能を持てる

ポータビリティ

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

http://utakotoha.codeplex.com/

再生中の曲に応じて日本語歌詞を表示する

マーケットプレイスで公開中(Free)

ソースコードも公開中

Utakotoha

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

コードは無理やり全部Rxで割と実験的

GUI周りは強引でボロボロで酷い

イベント周りなどは面白く仕上がったかも

Modelを別に立てた.NET4クラスライブラリにリンクで参照することによりMSTestでユニットテスト

Molesというモックライブラリを使ってWP7のイベント自体を乗っ取り(音楽再生の情報をテストのために任意に生成したり)

上手くいってるかはノーコメント

コードの中身

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

Deep Dive Rx

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

Data Developer Center - Rx

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

二つのドキュメントが出ています

Hands-on-Lab

チュートリアル式で触りながら分かりやすく

まず見て欲しい

Design Guidelines

マニュアルみたいなもの

更に深くへ

公式見るのがいいよやっぱり

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

ねぇよんなもん

うちのサイトでも見てください:)

http://neue.cc/

http://neue.cc/category/rx

日本語情報は?