会社で使う学習資料の更新が終わりました。毎回PowerPointのスライド20枚とサンプルプログラム1,2個つけたのが、今日の時点で13個。頑張ったものです。
Problem
WinFormsだと、画像オブジェクト、例えばSystem.Drawing.Bitmapは不要になったら、nullで参照を削除する前に、Disposeメソッドを呼び出す必要があります。
これは内部で保持している、GDIによるアンマネージドデータを破棄するために他なりません。
対して、GDIを使っていないWPF/UWPはどうなのでしょう? 無限ループでSystem.Windows.Media.Imaging.WriteableBitmapを生成するプログラムを組んでチェックしましたが、private working setが増えていたことから、アンマネージドでどこかにデータを確保しているのは間違いありませんでした。
でも、IDiposableインターフェイスは実装していません。
本当に参照が全部消えれば破棄されるの? 不安はぬぐい去れません。
五年前、入社二年目の私はインド人が手を入れた.NETをプログラムの修正を任されました。当時.NETを扱える人間が私しかいなかったためです。
で、そのソース、画像データを大量に扱う代物で、一切解放処理が書いてありませんでした。
当然しばらくするとメモリ不足で落ちました。4000x4000とか巨大なビットマップを扱うものですから… トルコまで行って対応したのはいい思い出です(白目)
Inspection
簡単なサンプルプログラムをWPF/UWPそれぞれ用意しました。
https://github.com/takuya-takeuchi/Demo/tree/master/UWP/UWP4
UWP側のViewModelだけ記載します。
1 | using System; |
Startボタンを押下すると、TextBoxで指定した、Width、HeightとCountで指定しただけのWriteableBitmapを生成して、System.Collections.Generic.Listに格納していきます。
Clearボタンを押下すると、List.Clearを呼びリストを空にします。
この時点で参照が全て消えるので、GCが動けばメモリが解放されるはずです。
一応明示的にGCを呼ぶためのGCボタンを用意しました。
また、WPFのWriteableBitmapはSystem.Windows.Freezableクラスの派生形であるため、Freezeメソッドをコールした場合、そうでない場合、挙動がどう変化するのかを確認します。
Try
計測は、dotMemoryを使います。
まずUWPです。
起動直後、Start直後、Clear直後、GC直後の4段階でSnapshotを採取します。
次に、Start直後とClear直後で比較をとります。
Listに含まれるWriteableBitmapのインスタンスが削除されていることがわかりますが、この時点ではメモリ量は変化しません。
最後の、GC直後の段階でメモリ使用量が大幅に減っているのがわかります。
同じようにWPFでもテストします。最初はFreezeしない状態で4つのSnapshotを採取します。
最後に、起動直後にFreezeのフラグを有効にして、WriteableBitmap生成直後にFreezeする処理を追加したパターンで4つのSnapshotを採取します。
Freezeの有無でメモリ使用量に変化があります。Freezeすると確保するメモリ量が60%位になりました。
Freeze状態にすると変更できなくなるため、データ内容を変更するために用意したバッファなどがなくなるのでしょうか? どちらにしても変更が不要なら、不要になった時点で即座にFreezeした方が良さそうです。
UWPも219.67MB-66.69MB=152.98MB
WPFも230.71MB-77.42MB=153.29MB
となり、
2000_2000_4(ARGB?)*10/1024/1024=152MB
いうデータ量に近似します。
メモリリークは発生していないと言えそうです。
Conclusion
WinFormsと異なり、画像データのメモリリークは起きないことが証明されました。
ただ、WinFormsとは違って、GCが発生しないとメモリが解放されないのはどうも…
明示的にGCを呼ぶのはWinFormsの時の経験から言って避けたいところです。明示的に呼んでフリーズしてそのまま何もできなくなってしまったことがあったので。
なにはともあれ、不安は消えました。
Source Code
https://github.com/takuya-takeuchi/Demo/tree/master/UWP/UWP4