More Related Content Similar to Reactive Extensionsで非同期処理を簡単に Similar to Reactive Extensionsで非同期処理を簡単に (20) More from Yoshifumi Kawai More from Yoshifumi Kawai (20) Reactive Extensionsで非同期処理を簡単に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
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);
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. Reactive Extensions =
Linq to Events
Linq to Asynchronous
LINQにおけるデータソースの拡張
……というだけじゃない
時間という軸を中心にした基盤
9. Rxの基本軸は時間
IE<T> length
IE<T>
async
event
IO<T> time
=> IE<T>も乗っかることで「全てのデータソース」が合成可能に!
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. しょうがないので非同期で書く
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. ネストするとかなりヤバい
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);
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. もはやカオスすぎて頭痛い
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. 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()));
内部で発生する例外は全てここで扱える
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. どっちがいいの?
Rxで使うならAPMのほうが相性良い
APMは上から下まで流れてるが、EAPは最後に発火
させなければならない
これはネストする場合に致命的に面倒
// APM
WebRequest.Create("http://hoge")
.GetResponseAsObservable()
.Subscribe();
// EAP
Subscribe後に発火
var wc = new WebClient();
wc.DownloadStringCompletedAsObservable()
.Subscribe();
wc.DownloadStringAsync("http://hoge");
23. FromEvent(FromEventPattern)
戻り値はIEvent<EventArgs>のIO<T>
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<EventHandler,EventArgs>
24. FromAsyncPattern
FromAsyncPatternの戻り値はデリゲート
つまり自分でInvokeするまで実行されない
拡張メソッドにするなら即実行のほうが便利?
Task.Factory.StartNew的なイメージで
public static IObservable<WebResponse>
GetResponseAsObservable(this WebRequest request)
{
return Observable.FromAsyncPattern<WebResponse>(
request.BeginGetResponse, request.EndGetResponse)
.Invoke();
}
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()));
29. ところでキャンセルしたい
// 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();
30. 例外処理は?
何も書かない
例外はcatchせずスローされてくる
SubscribeのonErrorに書く
Rxのチェーンで発生した例外を全てcatchする
ここに空のものを書けば例外無視が成立とかも
※Tips:一部メソッドが間に挟まれていると(Publishなど)
場合によってはcatchされなくなることも(Publishは内部
で自前でSubscribeしているためチェーンが途切れてる)
Catchメソッドを使う
例外の型を指定した通常のtry-catchに近いもの
戻り値にEmptyを返せば終了、何か別の値を返せばそ
れを代替として流すということになる
31. 基本的なのはこの3つ
// 何も書かないので例外がスローされてくる
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);
34. SelectMany + Zip - 合成の例
AsyncA AsyncB
Result
AsyncC
AsyncA().SelectMany(a => AsyncB(a))
.Zip(AsyncC(), (b, c) => new { b, c });
35. ForkJoin - 並列同時実行
AsyncA
AsyncB
Result
AsyncC
AsyncD
Observable.ForkJoin(AsyncA(), AsyncB(), AsyncC(), AsyncD())
.Select(xs => new { a=xs[0], b=xs[1], c=xs[2], d=xs[3] });
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 };
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(); // なにか処理するなり例外処理入れるなり
46. 公式見るのがいいよやっぱり
Data Developer Center - Rx
http://msdn.microsoft.com/en-us/data/gg577609
二つのドキュメントが出ています
Hands-on-Lab
チュートリアル式で触りながら分かりやすく
まず見て欲しい
Design Guidelines
マニュアルみたいなもの
更に深くへ