バックグラウンド スレッドで UI 要素を作るとメモリリークする (WPF)

題記の通りなのですが、一時ハマったので共有します。
正確には、「バックグラウンド スレッドで DispatcherObject を作るとメモリリークする」ですね。

例えば、バックグラウンドで画像をダウンロードして加工したり。
もしくは Grid や TextBlock といった UI 要素を使って、サムネイルやら何やらの画像を生成したいときとか。

ImageSource は Freezable なので、バックグラウンドで画像を作って Freeze() してしまえば、UI スレッドに渡してもだいじょうぶ。
なるべく UI スレッドの負担を減らしたいのです。


ということで、以下のようなコードを書いてみます。
サンプルなので Console アプリですが、PresentationCore, PresentationFramework, WindowsBase, System.Xaml あたりを参照すればよろし。

で、このビットマップを作るコードを、バックグラウンドスレッドで動かします。
試しに 10 回ほど。

ちなみに、バックグラウンドでやろうとすると「多数の UI コンポーネントが必要としているため、STA である必要があります。」といった例外がスローされるようなもの (XpsDocument とか) でも、Thread.SetApartmentState メソッドでスレッドを STA にしてやれば、ちゃんと動きます。

これを実行すると…

memory leak

メモリ使用量がどんどん増えていきます。
これではダメですね。

そこで、最初のコードに以下の 2 行を追加します。

ご覧の通り、Dispatcher をシャットダウンするコードです。
これを実行すると…

いい感じです。


さて、この原因についてですが。

DispatcherObject を生成した時点で、スレッドごとに新たな Dispatcher が生成されるため、…と考えられます。
(Dispatcher のソースコードを軽く読んだところ、スレッドごとに生成された Dispatcher は WeakReference で保持されてるようですが、果たして…?)

ご参考まで。
詳しい事情が判ったらまた展開します。


2014/05/20 追記:

@karno 氏が詳しい調査をしてくださいました。

バックグラウンドスレッドでUI要素を作るともっと問題は深刻かもしれない。(WPF)

Krile STARRYEYES に入った修正も参考にしつつ。メイン スレッド外で DispatcherObject を生成する場合は、かなり気を遣いましょう。

WPF に関する型ってだいたい DispatcherObject の派生型なので、気付かず使ってるパターンもありそうです。Dispatcher のシャットダウンまで面倒見てくれるユーティリティ等を用意したほうがよさげ、かな。

This entry was posted in 未分類. Bookmark the permalink.

One thought on “バックグラウンド スレッドで UI 要素を作るとメモリリークする (WPF)

  1. Pingback: バックグラウンドスレッドでUI要素を作るともっと問題は深刻かもしれない。(WPF) | LOGarithm

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です