Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9703

【PRISM】ダイヤログで使ったReactivePropertyは忘れず破棄しよう【WPF】

$
0
0
この記事は PRISMライブラリのDialog Serviceと、ReactivePropertyを併用する場合には ちゃんとReactivePropertyを破棄しよう!の話 Dialog Service内に限らず、使い終わったものは処分(破棄)するのがマナーです どういうこと? ReactivePropertyの後始末 - かずきのBlog@hatena ReactiveProperty作者様のブログ(↑コレ)から一部引用させていただくと・・・ Disposeしないといけないケース 他のIObservableをソースとしてReactivePropertyなんかを作ったときは、内部動作としてSubscribeをしているので、Disposeを呼ぶ必要があります。 で、どういうこと? さらに同記事から引用させていただくと・・・ こういう時は、ReactivePropertyを持っているクラスが不要になったタイミングでDisposeを呼んでやらないと、予期せぬ動作をすることがあるかもしれません。 この予期せぬ動作に遭遇したので、経緯と対策(私はどうしたか)を書き残す なぜDialog Serviceなのか? ダイヤログは、その性質上、呼び出しとクローズ処理が頻繁に行われる つまり(それがシングルトンでない場合)、頻繁にインスタンスが生成され、もしそれが適切なタイミングで破棄されなければ、メモリに悪影響を及ぼすことが予想される ダイヤログはその頻度に応じて悪影響の度合いが大きくなりがちであり また今回も自分はダイヤログを使っていて気付いた ⇒ 以上の理由から、この記事ではダイヤログをやり玉にあげている 検証用のコード デフォルトのMainWindowから自作MyDialog(ユーザーコントロール)を開くだけのアプリをつくる Prism.Wpfのバージョンは7.2.0をつかった モデルとダイヤログの準備 モデル モデルは一定間隔で連番を生成するObservablePropをもっている 超絶参考:【Reactive Extensions】 Hot変換はどういう時に必要なのか? - 問題の解決策「Hot変換」 ModelImpl.cs // コンストラクター内 ObservableProp = Observable .Timer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(2)) // はじめに3秒待って、2秒間隔 .Do(x => Debug.WriteLine($"モデル{nameof(ObservableProp)}: {x}")) // ただの表示 .Select(x => x) .Publish() .RefCount(); // 参考の記事を見ずに自力でやったときは // Select文の中にDebug.Write書いたり、Publish以下HOT変換してなかったり・・・ ダイヤログ ダイヤログは、モデルのObservableをもとにToReactivePropertyしてつくられた _reactivePropertyAAと_reactivePropertyBBをもっている MyDialogViewModel.cs // コンストラクター内 // Observableから流れてきた値と、ReactivePropertyの初期値を区別するために // -77,-88を初期値として与えている _reactivePropertyAA = model.ObservableProp.ToReactiveProperty(initialValue: -77); _reactivePropertyBB = model.ObservableProp.ToReactiveProperty(initialValue: -88); // _observerAA/BBは OnNext, OnCompletedをDebug.Writeするだけのオブザーバー _reactivePropertyAA.Subscribe(_observerAA); _reactivePropertyBB.Subscribe(_observerBB); Prism.Unity で DI モデルとダイヤログをそれぞれPrism.UnityのDIコンテナに登録する App.xaml.cs // Dependecy Injection用コンテナへ登録 protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterSingleton<IMyModel, ModelImpl>(); // モデル containerRegistry.RegisterDialog<MyDialog, MyDialogViewModel>(); // ダイヤログ } Case1: ReactivePropertyを破棄しなかった場合 メインウィンドウからダイヤログを開いて閉じる動作を3回行う 以下ログに説明を入れたもの。(キャプチャした時のログとは違うので、数字が異なる箇所もある) Case1_ReactivePropertyを破棄しなかった場合 // 出力結果 Show Dialog ボタンが押されました!! ModelImpl: コンストラクターが呼ばれました 17294952 // <- 末尾はGetHashCodeで得たインスタンス識別用 MyDialogViewModel: コンストラクターが呼ばれました 58713911 _reactivePropertyAA OnNext -77 // ReactiveProperty初期値 _reactivePropertyBB OnNext -88 MyDialog: ビューのコンストラクターが呼ばれました 50102218 OnDialogOpened モデルObservableProp: 0 _reactivePropertyAA OnNext 0 _reactivePropertyBB OnNext 0 モデルObservableProp: 1 _reactivePropertyAA OnNext 1 _reactivePropertyBB OnNext 1 モデルObservableProp: 2 _reactivePropertyAA OnNext 2 _reactivePropertyBB OnNext 2 Close Dialog ボタンが押されました!! // <- ここでダイヤログを閉じた OnDialogClosed // ダイヤログがクローズした後でも、OnNextが実行され続ける モデルObservableProp: 3 _reactivePropertyAA OnNext 3 _reactivePropertyBB OnNext 3 モデルObservableProp: 4 _reactivePropertyAA OnNext 4 _reactivePropertyBB OnNext 4 Show Dialog ボタンが押されました!! // <- もう1度ダイヤログを開く MyDialogViewModel: コンストラクターが呼ばれました 22985394 // <- 初めのダイヤログとは違うインスタンスが生成される(PRISM DialogServiceの挙動) _reactivePropertyAA OnNext -77 _reactivePropertyBB OnNext -88 MyDialog: ビューのコンストラクターが呼ばれました 35909463 OnDialogOpened MyDialog: ビューのデストラクターが呼ばれました 50102218 // <- ここで初めに開いたダイヤログのデストラクタが呼ばれる MyDialogViewModel: デストラクターが呼ばれました 58713911 // デストラクタが呼ばれるタイミングは事前にわからない モデルObservableProp: 5 _reactivePropertyAA OnNext 5 _reactivePropertyBB OnNext 5 _reactivePropertyAA OnNext 5 _reactivePropertyBB OnNext 5 モデルObservableProp: 6 _reactivePropertyAA OnNext 6 _reactivePropertyBB OnNext 6 _reactivePropertyAA OnNext 6 _reactivePropertyBB OnNext 6 モデルObservableProp: 7 _reactivePropertyAA OnNext 7 _reactivePropertyBB OnNext 7 _reactivePropertyAA OnNext 7 _reactivePropertyBB OnNext 7 Close Dialog ボタンが押されました!! // 2回目のダイヤログを閉じる OnDialogClosed モデルObservableProp: 8 _reactivePropertyAA OnNext 8 // <- 1回目, 2回目のダイヤログのオブザーバーが動いている _reactivePropertyBB OnNext 8 _reactivePropertyAA OnNext 8 _reactivePropertyBB OnNext 8 モデルObservableProp: 9 _reactivePropertyAA OnNext 9 _reactivePropertyBB OnNext 9 _reactivePropertyAA OnNext 9 _reactivePropertyBB OnNext 9 Show Dialog ボタンが押されました!! // <- 3回目のダイヤログを開く MyDialogViewModel: コンストラクターが呼ばれました 61469371 _reactivePropertyAA OnNext -77 _reactivePropertyBB OnNext -88 MyDialog: ビューのコンストラクターが呼ばれました 66482253 OnDialogOpened モデルObservableProp: 10 _reactivePropertyAA OnNext 10 _reactivePropertyBB OnNext 10 _reactivePropertyAA OnNext 10 _reactivePropertyBB OnNext 10 _reactivePropertyAA OnNext 10 _reactivePropertyBB OnNext 10 モデルObservableProp: 11 _reactivePropertyAA OnNext 11 // <- ダイヤログを開いた数 3 x ReactivePropertyの数 2 => _reactivePropertyBB OnNext 11 // 6 回の OnNext が実行されつづける _reactivePropertyAA OnNext 11 _reactivePropertyBB OnNext 11 _reactivePropertyAA OnNext 11 _reactivePropertyBB OnNext 11 ダイヤログが破棄された(デストラクタが呼ばれた)あとでも OnNextが実行されつづけており、不気味な感じがする。 この調子でダイヤログを開いて閉じてを繰り返すと ダイヤログから見捨てられたオブザーバーが延々とOnNextを実行しつづけて・・・ ダイヤログビューモデルクラスのprivate変数であるReactivePropertyと それをSubscribeしているオブザーバーが ダイヤログがいなくなってから(デストラクタ後)も存続し続けている(ように見える) この周辺の理解がまだ足りなく、もしかしたら重大な誤解をしているかもしれない(!?) Case2: ReactivePropertyを破棄した場合 ダイヤログビューモデルを変更する ReactivePropertyをnewしたタイミングで、同時に_disposablesにAddToしておいて ダイヤログが閉じたときに、呼ばれるOnDialogClosedメソッドの中で_disposablesをDispose()する MyDialogViewModel.cs // コンストラクター内 // 末尾に .AddTo() を付加した _reactivePropertyAA = model.ObservableProp.ToReactiveProperty(initialValue: -77).AddTo(_disposables); _reactivePropertyBB = model.ObservableProp.ToReactiveProperty(initialValue: -88).AddTo(_disposables); //Rxのdisposablesに破棄するものを詰め込んで、IDailogAwareのOnDialogClosedメソッドでDisposeする private readonly CompositeDisposable _disposables = new CompositeDisposable(); public void OnDialogClosed() => _disposables?.Dispose(); 実際に動作している動画 以下ログに説明を入れたもの。(キャプチャした時のログとは違うので、数字が異なる箇所もある) Case2_ReactivePropertyを破棄した場合 // 出力結果 Show Dialog ボタンが押されました!! ModelImpl: コンストラクターが呼ばれました 65158399 MyDialogViewModel: コンストラクターが呼ばれました 43339000 _reactivePropertyAA OnNext -77 // ReactiveProperty初期値 _reactivePropertyBB OnNext -88 MyDialog: ビューのコンストラクターが呼ばれました 14081900 // <- ここでダイヤログを開く OnDialogOpened モデルObservableProp: 0 _reactivePropertyAA OnNext 0 _reactivePropertyBB OnNext 0 モデルObservableProp: 1 _reactivePropertyAA OnNext 1 _reactivePropertyBB OnNext 1 モデルObservableProp: 2 _reactivePropertyAA OnNext 2 _reactivePropertyBB OnNext 2 Close Dialog ボタンが押されました!! OnDialogClosed _reactivePropertyAA Completed !! // ReactivePropertyのDisposeでOnCompleted()が呼ばれる _reactivePropertyBB Completed !! MyDialogViewModel: デストラクターが呼ばれました 43339000 MyDialog: ビューのデストラクターが呼ばれました 14081900 // 以後、コンソール出力なし おわりに 繰り返しになるが ダイヤログのビューとビューモデルが破棄された(デストラクタが呼ばれた)あとでも メンバ変数のオブザーバーが動きつづけるあたり、よくわからない なににせよ、よそのクラスのIObservableをToReactivePropertyしたときにはAddTo()することを 忘れないようにしておけばいいと思う ほか参考にさせていただいた記事など 本題よりも、RxのHot/Coldの理解に時間を割いた気がする Rx入門 (14) - Cold to Hot変換 Reactive Extensions再入門 その5「HotとCold」 Reactive Extensions再入門 その36「ColdからHotへ!Publishメソッドと参照カウンタ?RefCountメソッド」 Reactive Extensions再入門 その45「Scheduler」 ReactiveProperty を編む【step: 7 .NET Core WPF MVVM ReactiveProperty 入門 2020】 【ReactiveProperty】きちんとDisposeしよう | 泥庭

Viewing all articles
Browse latest Browse all 9703

Trending Articles