C# に限りませんが、.NET の BitmapDecoder の Create を別スレッドで呼び出すと、リソースリークします。
RegisterClassEx により登録されたウィンドウクラスが開放されないみたいです。32 ビットバージョンの Windows では、16000、64 ビットバージョンの Windows では、63000 回程度の繰り返しで、BitmapDecoder が作成できなくなります。
Windows XP の場合、RegisterClassEx で登録されたウィンドウクラスは、プロセスを終了しても残り、その他のほとんどのアプリケーションがまともに動作しなくなります。
Thread の代わりに、BackgroundWorker を使うと、リソースは自動的に開放されるみたいです。Dispatcher.CurrentDispatcher の InvokeShutdown() を呼ぶ方法もありますが、あまり、おすすめしません。詳しくはコードを見てください。
Do()、テストに成功した場合のみ、true を返すことにします。
using System; namespace SSS.Test { public abstract class Test { public abstract bool Do(); } }
path で読み込む画像ファイルのパスを指定します。適宜変更してください。
using System; using System.Windows.Media.Imaging; namespace UnitTest { class BitmapDecoderTest : SSS.Test.Test { // const string path = @"C:\Windows\Web\Wallpaper\img1.jpg"; const string path = @"C:\WINDOWS\Web\Wallpaper\夕陽の砂丘.jpg"; const BitmapCreateOptions createOption = BitmapCreateOptions.None; const BitmapCacheOption cacheOption = BitmapCacheOption.Default; public override bool Do() { BitmapDecoder decoder = BitmapDecoder.Create( new Uri(path), createOption, cacheOption ); return true; } } }
Test() を呼び出すと、「別スレッドで、引数 test の Do() を実行、終了を待つ」を、無限に繰り返します。invokeShutdown を true にするとリソースリークしません。つまり、CurrentDispatcher の InvokeShutdown() を呼ぶことでリソースを開放できます。
using System; using System.Threading; using System.Windows.Threading; namespace SSS.Test { public class ThreadTester { public bool EndTest = false; protected Test test; protected bool invokeShutdown = false; public ThreadTester(Test test, bool invokeShutdown) { this.test = test; this.invokeShutdown = invokeShutdown; } public void Do() { try { EndTest = !test.Do(); } catch (Exception err) { EndTest = true; Console.WriteLine(err); } finally { if (invokeShutdown) { Dispatcher dsp = Dispatcher.FromThread(Thread.CurrentThread); if (dsp != null) dsp.InvokeShutdown(); } } } public static void Test(Test test, bool invokeShutdown) { var tester = new ThreadTester(test, invokeShutdown); for (int i = 0;; ++ i) { Thread thread = new Thread(new ThreadStart(tester.Do)); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); if (i % 100 == 99) Console.WriteLine(i + 1); if (tester.EndTest) break; } } } }
ThreadTester.Test() の引数で、invokeShutdown を false に設定しているのでリークします。(2) のように、true にすると一応、リークしません。(3) のように、BackgroundWorkerTester を使うと、さらに良い結果になります。
using System; using SSS.Test; namespace UnitTest { class Program { [STAThread] static void Main(string[] args) { ThreadTester.Test(new BitmapDecoderTest(), false); // (1) // ThreadTester.Test(new BitmapDecoderTest(), true); // (2) // BackgroundWorkerTester.Test(new BitmapDecoderTest()); // (3) } } }
上の (3) に対応する、BackgroundWorkerTester です。こちらでは、InvokeShutdown を呼ばなくてもリソースリークしません。
Thread を使って InvokeShutdown した場合には、少しずつ、メモリーの使用量が増え、33 MByte くらいで安定しますが、BackgroundWorker を使用した場合、実行してすぐに、22 MByte くらいで安定します。(Windows XP x64 の場合)
想像するに、Thread を使った場合には、ガベージコレクターまかせで開放されることになる、ネイティブリソースがあるみたいです。うーん、何なんだ?
using System; using System.ComponentModel; namespace SSS.Test { public class BackgroundWorkerTester { public bool EndTest = false; protected Test test; public BackgroundWorkerTester(Test test) { this.test = test; } public void Do(object sender, DoWorkEventArgs e) { try { EndTest = !test.Do(); } catch (Exception err) { EndTest = true; Console.WriteLine(err); } } public static void Test(Test test) { var tester = new BackgroundWorkerTester(test); for (int i = 0;; ++ i) { var worker = new BackgroundWorker(); worker.DoWork += tester.Do; worker.RunWorkerAsync(); while (worker.IsBusy) System.Threading.Thread.Sleep(1); if (i % 100 == 99) Console.WriteLine(i + 1); if (tester.EndTest) break; } } } }
BackgroundWorker では、Thread に加えて、謎のリソースの開放処理が入っているみたいなので、より安全です。Thread で行っていた部分を BackgroundWorker で置き換えるのは、機能が増えるだけなので簡単です。
この Tips は、ミルノ PC フォトフレーム のメモリーリークを解消する際に必要となった知識です。画像の読み込みは、別スレッドで行うのは自然なのに、MSDN には、何の注意もありません。不思議ですね・・・。
このサイトのページへのリンクは自由に行っていただいてかまいません。
このサイトで公開している全ての画像、プログラム、文書の無断転載を禁止します。
ここをクリック
すると表示されるページから作者へメールで連絡できます。