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 には、何の注意もありません。不思議ですね・・・。
このサイトのページへのリンクは自由に行っていただいてかまいません。
このサイトで公開している全ての画像、プログラム、文書の無断転載を禁止します。
ここをクリック
すると表示されるページから作者へメールで連絡できます。