私は、複数の画像をキャンバスにレンダリングし、最後にビットマップまたは PNG をプッシュする (そしてこれを複数回行う) サービスのリークを診断するのにかなり苦労しました。起動すると、サービスは 600MB 以上まで急激に増加し、取得できる容量がほぼすべて使用されるまで増加し続けます。一度始めると減ることはありません。
VS2012 perf mon を使用してサービスをインストルメント化して実行しましたが、処理が完了した後に大量のバイト配列が配置されていることがわかりました。GCクリアを試して、それらが無駄になるかどうかを確認しました. WinDbg を使用して調べてみると、イメージを RAM に保持しているアイテム (主に WPF オブジェクト) の長いチェーンによってバイト配列が保持されていることがわかります。
使用されているすべてのオブジェクトについて MSDN を調べましたが、コードの実行方法に関する問題を指摘するものは何も見つかりません。
サービスに厳密に従ういくつかのサンプル コードをまとめました (いくつかのビットは、便利にするために省略されています)。これは、スレッドから呼び出されます (STA に設定されます)。他の唯一の違いは、サービス コードが、使用している URI ではなく、DB から供給されたイメージを読み込むために MemoryStreams を使用することです。ストリームは次のように破棄されます。
public static void TestThread()
{
const int rounds = 50;
const int innerRounds = 50;
var randomiser = new Random(DateTime.Now.Millisecond);
// Simulating some live values
const float scaling = 2.67F;
const int pageWidth = 363;
const int pageHeight = 516;
const int dpi = 96;
// To simulate the live system using multiple images
// This is an list of images of different sizes etc
var imageList = new List<ImageData>
{
new ImageData{Uri = new Uri(@"..."), Height = 2592},
new ImageData{Uri = new Uri(@"..."), Height = 1339},
new ImageData{Uri = new Uri(@"..."), Height = 386},
new ImageData{Uri = new Uri(@"..."), Height = 968},
new ImageData{Uri = new Uri(@"..."), Height = 1952},
new ImageData{Uri = new Uri(@"..."), Height = 1024},
};
var proc = Process.GetCurrentProcess();
for (var i = 0; i < rounds; ++i)
{
var canvas = new Canvas();
canvas.BeginInit();
canvas.SnapsToDevicePixels = false;
canvas.UseLayoutRounding = false;
canvas.Width = pageWidth;
canvas.Height = pageHeight;
canvas.Background = Brushes.White;
for (var j = 0; j < innerRounds; ++j)
{
var img = new Image {Stretch = Stretch.Fill};
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
var imageNo = randomiser.Next(0, imageList.Count - 1);
bitmapImage.UriSource = imageList[imageNo].Uri;
int imageHeight = imageList[imageNo].Height;
bitmapImage.DecodePixelHeight = (int) (imageHeight * scaling * 1.8);
bitmapImage.EndInit();
if (bitmapImage.CanFreeze)
{
bitmapImage.Freeze();
}
var opacityMask = new ImageBrush();
var opactityBitmap = new BitmapImage();
opactityBitmap.BeginInit();
opactityBitmap.CacheOption = BitmapCacheOption.OnLoad;
imageNo = randomiser.Next(0, imageList.Count - 1);
opactityBitmap.UriSource = imageList[imageNo].Uri;
int opacityImageHeight = imageList[imageNo].Height; ;
opactityBitmap.DecodePixelHeight = (int)(opacityImageHeight * scaling * 1.8);
opactityBitmap.EndInit();
if (opactityBitmap.CanFreeze)
{
opactityBitmap.Freeze();
}
opacityMask.ImageSource = opactityBitmap;
img.OpacityMask = opacityMask;
img.Source = bitmapImage;
img.Width = pageWidth * scaling;
img.Height = pageHeight * scaling;
Canvas.SetLeft(img, 0);
Canvas.SetTop(img, 0);
canvas.Children.Add(img);
img.Opacity = 50F;
}
canvas.LayoutTransform = null;
var size = new Size(Math.Max(canvas.Width, 5), Math.Max(canvas.Height, 5));
canvas.Measure(size);
canvas.Arrange(new Rect(size));
canvas.EndInit();
var renderTargetBitmap = new RenderTargetBitmap(pageWidth, pageHeight, dpi, dpi, PixelFormats.Default); //xxx
renderTargetBitmap.Render(canvas);
if (renderTargetBitmap.CanFreeze)
{
renderTargetBitmap.Freeze();
}
System.Drawing.Image imageData;
using (var ms = new MemoryStream())
{
var image = new PngBitmapEncoder();
image.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
image.Save(ms);
imageData = System.Drawing.Image.FromStream(ms);
}
var encoder = Encoder.Quality;
var encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(encoder, 100L);
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
var codecInfo = codecs.FirstOrDefault(x => x.FormatID == ImageFormat.Png.Guid);
Byte[] bitmapArray;
using (var ms = new MemoryStream())
{
imageData.Save(ms, codecInfo, encoderParams);
bitmapArray = ms.GetBuffer();
}
var filepath = string.Format(@"C:\temp\{0}.png", i);
imageData.Save(filepath, ImageFormat.Png);
var gcMemory = GC.GetTotalMemory(false) / 1024;
Console.WriteLine("Proc mem = {0}KB, GC = {1}KB", (proc.PrivateMemorySize64 / 1024), gcMemory);
}
Console.WriteLine("Exiting");
}
私がここで見逃しているのは明らかなことだと思っています。ご助力いただきありがとうございます!