題記の通りなのですが、一時ハマったので共有します。
正確には、「バックグラウンド スレッドで DispatcherObject を作るとメモリリークする」ですね。
例えば、バックグラウンドで画像をダウンロードして加工したり。
もしくは Grid や TextBlock といった UI 要素を使って、サムネイルやら何やらの画像を生成したいときとか。
ImageSource は Freezable なので、バックグラウンドで画像を作って Freeze() してしまえば、UI スレッドに渡してもだいじょうぶ。
なるべく UI スレッドの負担を減らしたいのです。
ということで、以下のようなコードを書いてみます。
サンプルなので Console アプリですが、PresentationCore, PresentationFramework, WindowsBase, System.Xaml あたりを参照すればよろし。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
static void Work() { var border = new Border { Background = Brushes.Red }; var text = new TextBlock { Text = "hoge" }; border.Child = text; border.Arrange(new Rect(0, 0, 200, 200)); var bitmap = new RenderTargetBitmap(200, 200, 96, 96, PixelFormats.Default); bitmap.Render(border); bitmap.Freeze(); // 生成したビットマップ使って何かする } |
で、このビットマップを作るコードを、バックグラウンドスレッドで動かします。
試しに 10 回ほど。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
static void Main(string[] args) { for (var i = 0; i < 10; i++) { var thread = new Thread(Work) { Name = "Bitmap worker: " + i, IsBackground = true, }; thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); Console.WriteLine("Total Memory = {0} KB", GC.GetTotalMemory(true) / 1024); } Console.ReadLine(); } |
ちなみに、バックグラウンドでやろうとすると「多数の UI コンポーネントが必要としているため、STA である必要があります。」といった例外がスローされるようなもの (XpsDocument とか) でも、Thread.SetApartmentState メソッドでスレッドを STA にしてやれば、ちゃんと動きます。
これを実行すると…
メモリ使用量がどんどん増えていきます。
これではダメですね。
そこで、最初のコードに以下の 2 行を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
static void Work() { var border = new Border { Background = Brushes.Red }; var text = new TextBlock { Text = "hoge" }; border.Child = text; border.Arrange(new Rect(0, 0, 200, 200)); var bitmap = new RenderTargetBitmap(200, 200, 96, 96, PixelFormats.Default); bitmap.Render(border); bitmap.Freeze(); // 生成したビットマップ使って何かする Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.SystemIdle); Dispatcher.Run(); } |
ご覧の通り、Dispatcher をシャットダウンするコードです。
これを実行すると…
いい感じです。
さて、この原因についてですが。
DispatcherObject を生成した時点で、スレッドごとに新たな Dispatcher が生成されるため、…と考えられます。
(Dispatcher のソースコードを軽く読んだところ、スレッドごとに生成された Dispatcher は WeakReference で保持されてるようですが、果たして…?)
ご参考まで。
詳しい事情が判ったらまた展開します。
2014/05/20 追記:
@karno 氏が詳しい調査をしてくださいました。
Krile STARRYEYES に入った修正も参考にしつつ。メイン スレッド外で DispatcherObject を生成する場合は、かなり気を遣いましょう。
WPF に関する型ってだいたい DispatcherObject の派生型なので、気付かず使ってるパターンもありそうです。Dispatcher のシャットダウンまで面倒見てくれるユーティリティ等を用意したほうがよさげ、かな。
Pingback: バックグラウンドスレッドでUI要素を作るともっと問題は深刻かもしれない。(WPF) | LOGarithm