-
Notifications
You must be signed in to change notification settings - Fork 0
MS_ReactiveExtensions
- 戻る(非同期処理)
-
Observer パターンを実装するフレームワークだが、
-
特に、LINQ to Events もしくは LINQ to Asynchronous と表現できる。
-
これは、要するに、
- イベントや非同期処理を LINQ っぽく扱える。
- 非同期 / イベント / 時間に関する処理を LINQ 的に簡潔かつ宣言的に記述できる。
と言うトコロを意味する。
移行メモ(表記): 原文の「LINQ to Asynchronus」は
Asynchronous の綴りの誤りと判断し修正した。
補足(
IEnumerableとIObservableは双対): Rx を理解する鍵は、
IObservable<T>がIEnumerable<T>の裏返しである、という点にある。
IEnumerable<T>(Pull)IObservable<T>(Push)主導権 受け手が MoveNext()で取りに行く送り手が OnNext()で押し込む終了 MoveNext()が falseOnCompleted()異常 例外が throw される OnError()演算子 Where/Select/ …同じ名前の演算子が使える 「値の並び」であることは同じなので、
LINQ の演算子がそのまま通用する。
これが「イベントを LINQ で扱える」という発想の正体である。// 「クリックが 300ms 以内に 2 回」= ダブルクリック、を宣言的に書く clicks.Buffer(clicks.Throttle(TimeSpan.FromMilliseconds(300))) .Where(x => x.Count == 2) .Subscribe(_ => Console.WriteLine("double click"));時間を第一級に扱える点が、Rx が今も価値を持つ理由である。
-
Silverlight Toolkit に "System.Reactive.dll" が同梱される。
-
Reactive Framework ---> Reactive Extensions と名称変更。
-
DevLabs(昔の MS コミュニティサイト)でプロジェクト公開
ソースコードは(恐らく)CodePlex で公開されていた。 -
何度も API の消滅・追加を繰り返す。
-
JavaScript 版が登場(RxJS)
-
.NET Framework 4 SP1 に標準搭載
-
さまざまな開発言語に移植され、幅広く使われている。
- JavaScript 用の RxJS
- Java / Android 用の RxJava
- Swift 用の RxSwift
- Unity 用の UniRx
補足(その後/最新化): 原文以降の動きを補う。
時期 内容 2016 頃 ReactiveX として言語横断の仕様・サイトに集約 Rx.NET が .NET Foundation 配下でメンテナンスされる 2019 IAsyncEnumerable<T>が C# 8.0 / .NET Core 3.0 に標準搭載現在 System.Reactiveパッケージとして提供(Rx.NETリポジトリ)Unity UniRx → R3 / UniTask に世代交代が進む 重要な変化は
IAsyncEnumerable<T>の登場である。
「非同期に届く値の並びを扱う」という Rx の用途の一部が、
言語機能(await foreach)で素直に書けるようになった。// 昔は Rx が必要だった処理が、標準構文で書ける await foreach (var item in GetItemsAsync()) Console.WriteLine(item);現在の使い分けの目安は次の通り。
やりたいこと 適した手段 1 回の非同期処理 async/await 非同期に届く有限の並び(Pull) IAsyncEnumerable<T>時間・イベントの合成(Push) Rx UI のイベント処理・デバウンス Rx Rx は「万能の非同期基盤」から
**「イベントと時間の合成に特化した道具」**へと位置づけが定まった、
と理解するのが実態に近い。
通知に対して、LINQ(フィルタ、変換、集計、合成)を適用できる。
イベントに Observer パターンを適用して、
LINQ(フィルタ、変換、集計、合成)を適用できる。
-
イベントに Observer パターンを適用して、
LINQ(フィルタ、変換、集計、合成)を適用できる。 -
具体的には、Observer を合成してネストを処理が可能。
Observer パターンで構成される。
- 何らかのProvider(IObservable<T> など)で監視を行い、
- Providerから状態の Push(=通知、発行、イベント)を、
- Subscriber(IObserver<T> など)が受け取る。
IObserver<T> を実装したクラスを定義する。
- IObservable<T> を実装したクラスを定義する。
- クラスはラムダ式を使用したメソッドチェーンで実装できる。
- Create などの生成メソッドで生成、
| # | メソッド | 概要 |
|---|---|---|
| 1 | Create | 監視内容を定義する。 |
| 2 | Return | 渡した値を単純に通知する。 |
| 3 | Range | 指定した範囲の値を通知する。 |
| 4 | Repeat | 第 1 引数で渡したデータを、 第 2 引数で指定した回数繰り返して通知する。 |
| 5 | Generate | for 文的に以下を定義する。 ・初期値 ・継続判定デリゲート ・インクリメント・デリゲート ・通知する値を生成するデリゲート |
| 6 | Case | 複数用意された IObservable の中から、どれか1つを選択。 ・IObservable 配列 ・IObservable 選択デリゲート |
| 7 | Throw | 例外を通知する。 |
| 8 | Start | 渡した値を非同期で通知する。 |
| 9 | Defer | Provider のファクトリを実装。 |
移行メモ(誤字):
Rangeの説明「正指定した範囲の値」は
指定した範囲の値の誤字と判断し修正した。
- Create で生成する場合、以下のメソッドを使用して監視内容を定義する。
| # | メソッド | 概要 |
|---|---|---|
| 1 | OnNext | 新しい値が発生したことを通知する。 |
| 2 | OnError | エラーが発生し、異常終了したことを通知する。 |
| 3 | OnCompleted | 正常に終了したことを通知する。 |
補足(Rx の文法規則): この 3 つには厳密な順序の約束がある。
OnNext* (OnError | OnCompleted)?つまり、
OnNextは 0 回以上流れるOnErrorまたはOnCompletedは どちらか一方が、最後に 1 回だけ- 終了通知の後には、何も流れてはならない
自分で
IObservable<T>を実装する場合、この規則を守る責任は
実装側にある。Observable.Createを使えば大部分は担保される。
-
必要に応じて、LINQ メソッドを使用し通知内容を操作できる。
- Where(フィルタリング)
- Select(変換)
- Aggregate(集計)
- 合成
-
Subscribe で、上記の其々のメソッドからの
Push(=通知、発行、イベント)を受け取るSubscriberを設定する。
- Provider が OnCompleted を呼び出し、正常終了。
- Provider が OnError を呼び出し、異常終了。
- Subscriber側で Dispose する。
補足(購読解除の忘れがリークになる):
Subscribeは
IDisposableを返す。これを捨てると購読が残り続け、
Provider が Subscriber を参照し続けるためメモリ リークになる。// ① using / Dispose で明示的に解除 using var sub = observable.Subscribe(...); // ② CompositeDisposable にまとめて、画面破棄時に一括解除 _disposables.Add(observable.Subscribe(...)); // ③ 寿命を別のストリームに委ねる observable.TakeUntil(_closed).Subscribe(...);
OnErrorで購読が終了する点も重要で、
エラー後も流し続けたい場合はRetry/Catchを挟む必要がある。
大きく分けて Hot / Cold に分類される。
-
Cold
- Subscriberとは1:1の関係
- Subscriberが居なければ動作しない。
- 監視が終わった後は OnCompleted で(自分で)終了する。
- Observable.Interval など例外もある。
-
Hot
- Subscriberとは1:nの関係
- Subscriberがいなくても動作し続ける。
- 監視が終わったかどうかSubscriberが判断して Dispose。
補足(Hot / Cold は Rx 最大のハマりどころ): 原文が
後述のポイントで「概念を掴む」と強調している通り、
ここが最も事故が起きる箇所である。
Cold Hot 値の生成 購読のたびに最初から生成 1 本の流れを共有 例 Observable.Range、HTTP リクエストイベント、 Subject、株価購読前の値 失われない(購読時に始まる) 購読前の値は受け取れない 典型的な事故:
var src = Observable.Create<int>(o => { Console.WriteLine("実行"); ... }); src.Subscribe(...); // 「実行」 src.Subscribe(...); // 「実行」← 2 回動いてしまうHTTP リクエストなど副作用のある Cold を複数箇所で購読すると、
リクエストが購読数だけ飛ぶ。対処は Cold を Hot に変換すること。
var shared = src.Publish().RefCount(); // 購読者間で 1 本を共有
- IObserver<T> と IObservable<T> を実装したクラスを定義できる。
- Subject を使用すれば、Subscriber をラムダ式で定義できる。
補足(Subject の種類と、使いすぎへの注意):
種類 挙動 Subject<T>購読後に流れた値のみ受け取る BehaviorSubject<T>最新の 1 件を購読時に即座に受け取る(状態の表現に適する) ReplaySubject<T>過去 n 件を購読時に再生 AsyncSubject<T>完了時に最後の 1 件だけ流す( Taskに近い)ただし、
Subjectを安易に使うと Rx の宣言的な利点が失われる
(手続き的にOnNextを呼ぶコードが散らばる)。
可能ならObservable.FromEventや演算子で組み立て、
Subjectは外部との境界に限定するのが定石である。
- Merge
- SelectMany
- Switch
- Concat
- Zip
- Amb
- CombineLatest
補足(実務で使う頻度が高いもの): 数百ある演算子のうち、
実際によく使うのは限られる。
演算子 用途 Throttle(RxJS の debounce)入力が止まってから実行(インクリメンタル検索) Sample一定間隔で最新値を間引く Switch新しい要求が来たら、前の処理を捨てる(検索の競合防止) CombineLatest複数の最新値を組み合わせる(フォームの妥当性判定) Retry/Catchエラー時の再試行 DistinctUntilChanged同じ値の連続を無視
Throttle+Switchの組み合わせは、
「入力のたびに検索 API を叩き、古い結果が後から返って上書きする」
という古典的なバグを、宣言的に解決する。textChanged .Throttle(TimeSpan.FromMilliseconds(300)) // 入力が落ち着いてから .DistinctUntilChanged() // 同じ語なら投げない .Select(q => SearchAsync(q).ToObservable()) .Switch() // 古い検索は破棄 .Subscribe(UpdateUI);
MergeとConcatの違いにも注意。
Mergeは並行、Concatは前が終わってから次である。
-
FromEvent メソッドで、
イベント・クラスを IObservable に変換できる。 -
LINQ メソッドを使用し通知内容を操作できる。
- 具体的には、ドラッグ操作を簡単に記述できる。
-
参考
- Rx入門 (19) - マウス位置のトラッキング - xin9le.net
http://blog.xin9le.net/entry/2012/02/13/233250 - マウスのドラッグ イベントを Rx で実装する | Do Design Space
https://sakapon.wordpress.com/2016/10/28/reactive-drag/ - Reactive Extensionsを使ったWPFのドラッグアンドドロップをまとめる - Qiita
https://qiita.com/ledsun/items/1ab669ecbea103972e7d
- Rx入門 (19) - マウス位置のトラッキング - xin9le.net
補足(ドラッグが Rx の代表例である理由): ドラッグは
「MouseDown してから MouseUp するまでの MouseMove」であり、
イベントの時間的な組み合わせそのものである。var drag = from down in mouseDown from move in mouseMove.TakeUntil(mouseUp) select move.Location;手続き的に書くと「ドラッグ中フラグ」と開始座標の状態管理が要るが、
Rx では状態変数を持たずに表現できる。
-
FromAsyncPattern メソッドで、
BeginInvoke/EndInvoke を IObservable に変換できる。 -
LINQ メソッドを使用し通知内容を操作できる。
- 具体的には、
- SelectMany を組み合わせ、
- 非同期のネストを簡単に記述できる。
- 具体的には、
-
参考
- Rx入門 (12) - 非同期処理のシーケンス化 - xin9le.net
http://blog.xin9le.net/entry/2012/01/15/163827 - 非同期でのリクエスト処理をRxでということの取り組みのメモ - かずきのBlog@hatena
- Rx入門 (12) - 非同期処理のシーケンス化 - xin9le.net
補足(現在は
async/awaitが担う領域/最新化): この節が扱う
APM(Begin~/End~)のネスト解消は、
Rx が登場した当時(.NET 4 以前)における最大の動機の一つだった。しかし C# 5.0 の async/await により、
「非同期のネスト」は言語レベルで解決された。// かつて Rx / SelectMany で解いていた問題 var a = await GetAAsync(); var b = await GetBAsync(a);現在の Rx と Task の相互変換は次の通り。
変換 メソッド Task→IObservable.ToObservable()IObservable→Taskawait observable(最後の値)、.ToTask()IObservable→IAsyncEnumerable.ToAsyncEnumerable()なお
FromAsyncPatternは
APM 自体が非推奨(Begin~/End~は現在ほぼ使われない)のため、
新規コードではObservable.FromAsyncを使う。
補足(Scheduler の要点): Rx ではどのスレッドで動くかを
演算子ではなく Scheduler が決める。
メソッド 何を変えるか ObserveOn通知を受け取る(=下流が動く)スレッド SubscribeOn**購読処理(=上流の開始)**が動くスレッド UI アプリでは、
**「重い処理はバックグラウンド、UI 更新は UI スレッド」**を
この 2 つで宣言的に指定できる。source .SubscribeOn(TaskPoolScheduler.Default) // 取得はプールで .Select(Heavy) .ObserveOn(DispatcherScheduler.Current) // 表示は UI スレッドで .Subscribe(UpdateUI);既定では、Rx はスレッドを勝手に切り替えない
(OnNextを呼んだスレッドがそのまま下流を実行する)点に注意。
補足(テストの本命は TestScheduler): 原文の言う
「Subject でテストする」(任意のタイミングで値を流し込む)に加え、
Rx にはTestScheduler(Microsoft.Reactive.Testing)がある。仮想時間を進められるため、
Throttle(5分)のような処理を
実際に 5 分待たずにテストできる。var s = new TestScheduler(); var result = s.Start(() => source.Throttle(TimeSpan.FromMinutes(5), s)); // 5 分を「即座に」進めて検証できる時間に依存する処理を確実にテストできるという点は、
手続き的な実装に対する Rx の明確な優位性である。
2020/03/09 ちょっとした事件が起きたらしい。
ファクト・ファインディング
-
Qiita
-
UniRxを使った開発をして思ったこと
https://qiita.com/nodead/items/781ad247c127af47a1a2 -
プログラム開発者格差の話をしよう
https://qiita.com/Rwf-9DH3/items/fe65756c0485f095cc98「なるほど完璧な作戦っスね―――ッ
不可能だという点に目をつぶればよぉ~~~」
(引用:ジョジョの奇妙な冒険 第4部)
-
-
イイね。
-
SI っぽくなって来た。
-
dis っては無い。
「そう言う事も考える必要が出てきた。」
...と言う事で。
補足(この「事件」の論点): 参照先は、
「Rx は強力だが、チーム全体が使いこなせるとは限らない」
という、技術選定における習熟度格差の話題である。Rx は学習曲線が急で、
- Hot / Cold を理解していないと、副作用が多重に走る
- 購読解除を忘れるとリークする
- 演算子が数百あり、レビューで妥当性を判断しづらい
- スタック トレースが追いにくく、デバッグが難しい
といった性質を持つ。原文の「SI っぽくなって来た」という感想は、
個人の技量に依存する技術を、多人数の開発に持ち込む難しさを
指したものと読める。現在の妥当な判断としては、
- 単発の非同期は
async/await、- 有限の非同期列は
IAsyncEnumerable<T>、- 本当に「時間とイベントの合成」が要る箇所に限って Rx、
と切り分けるのが、この論点への実務的な回答になる。
- Reactive Extensions
https://learn.microsoft.com/en-us/previous-versions/dotnet/reactive-extensions/hh242985(v=vs.103) - dotnet/reactive: The Reactive Extensions for .NET
https://github.com/dotnet/reactive - ReactiveX
http://reactivex.io/
移行メモ(リポジトリ移管): 原文の
Reactive-Extensions/Rx.NETは、現在dotnet/reactiveに移管
(.NET Foundation 配下)されているため、URL を更新した。
- Reactive Extensions(Rx)入門
http://www.atmarkit.co.jp/fdotnet/introrx/index/index.html- 第1回 Reactive Extensionsの概要と利用方法
http://www.atmarkit.co.jp/fdotnet/introrx/introrx_01/introrx_01_01.html - 第2回 イベント・プログラミングとRx
http://www.atmarkit.co.jp/fdotnet/introrx/introrx_02/introrx_02_01.html
- 第1回 Reactive Extensionsの概要と利用方法
- 各言語に広まったRx(Reactive Extensions、ReactiveX)の現状・これから
https://www.buildinsider.net/column/kawai-yoshifumi/004
-
いまさら聞けないReactive Extensions
-
こわくないReactive Extensions超入門
https://qiita.com/acple@github/items/6cfee916f09632037a6e -
C#とRxでリアクティブプログラミングしてみた。
https://qiita.com/dotKomu/items/198d43d63eab08b2fafb
-
Reactive Extensions入門 + メソッド早見解説表
http://neue.cc/2010/07/28_269.html- メソッド探訪
- 第1回 : FromEvent
http://neue.cc/2009/09/04_197.html - 第2回 : AnonymousEnumerable
http://neue.cc/2009/09/07_198.html - 第3回 : Subscribe
http://neue.cc/2009/09/13_200.html - 第4回 : メソッド一覧
http://neue.cc/2009/09/14_201.html - 第5回 : Scan
http://neue.cc/2009/09/28_205.html - 第6回 : exception handling
http://neue.cc/2009/11/29_226.html - 第7回 : IEnumerable vs IObservable
http://neue.cc/2010/06/24_263.html
- 第1回 : FromEvent
- メソッド探訪
- Rx入門
http://blog.xin9le.net/entry/rx-intro - Hokuriku.NET C# -Rx入門- 資料
http://blog.xin9le.net/entry/2012/03/19/120921
- Reactive Extensions カテゴリーの記事一覧
http://blog.okazuki.jp/archive/category/Reactive%20Extensions - Reactive Extensionsのはじめかた
http://blog.okazuki.jp/entry/2015/03/23/203825 - Reactive Extensions入門「まとめ」
http://blog.okazuki.jp/entry/20101002/1286011016 - Reactive Extensions再入門
- その46「 Reactive Extensions 入門 」
http://blog.okazuki.jp/entry/20120305/1329923070
- その46「 Reactive Extensions 入門 」
Reactive Extensions 入門
- (1)
http://tnakamura.hatenablog.com/entry/20110113/reactive_extensions_1 - (2)
http://tnakamura.hatenablog.com/entry/20110121/reactive_extensions_2 - (3)
http://tnakamura.hatenablog.com/entry/20110124/reactive_extensions_3 - (4)
http://tnakamura.hatenablog.com/entry/20110126/reactive_extensions_4 - (5)
http://tnakamura.hatenablog.com/entry/20110201/reactive_extensions_5
Reactive Extensions (Rx)
- Part 1 - Replacing C# Events
http://rehansaeed.com/reactive-extensions-part1-replacing-events/ - Part 2 – Wrapping C# Events
http://rehansaeed.com/reactive-extensions-part2-wrapping-events/ - Part 3 – Naming Conventions
http://rehansaeed.com/reactive-extensions-part3-naming-conventions/ - Part 4 – Replacing Timers
http://rehansaeed.com/reactive-extensions-part4-replacing-timers/ - Awaiting Observables
http://rehansaeed.com/reactive-extensions-part5-awaiting-observables/ - Part 6 – Task ToObservable
http://rehansaeed.com/reactive-extensions-part6-task-toobservable/ - Part 7 – Sample Events
http://rehansaeed.com/reactive-extensions-part7-sample-events/ - Part 8 – Timeouts
http://rehansaeed.com/reactive-extensions-rx-part-8-timeouts/
Tags: 移行, .NET開発
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。