3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

この記事は

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回行う
Animation.gif

以下ログに説明を入れたもの。(キャプチャした時のログとは違うので、数字が異なる箇所もある)

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を破棄した場合

ダイヤログビューモデルを変更する

ReactivePropertynewしたタイミングで、同時に_disposablesAddToしておいて
ダイヤログが閉じたときに、呼ばれるOnDialogClosedメソッドの中で_disposablesDispose()する

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

実際に動作している動画

Animation2.gif

以下ログに説明を入れたもの。(キャプチャした時のログとは違うので、数字が異なる箇所もある)

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
// 以後、コンソール出力なし

おわりに

繰り返しになるが
ダイヤログのビューとビューモデルが破棄された(デストラクタが呼ばれた)あとでも
メンバ変数のオブザーバーが動きつづけるあたり、よくわからない

なににせよ、よそのクラスのIObservableToReactivePropertyしたときにはAddTo()することを
忘れないようにしておけばいいと思う

ほか参考にさせていただいた記事など

本題よりも、RxのHot/Coldの理解に時間を割いた気がする

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?