WriteableBitmap内存泄漏?

时间:2013-02-05 15:20:01

标签: c# windows-phone writeablebitmap live-tile

我正在使用以下代码基于UI元素创建实时图块。 它在uiElement上呈现WriteableBitmap,保存位图+返回文件名。 此方法在Windows Phone后台任务代理中运行,而我正在运行内存限制。

private string CreateLiveTileImage(Canvas uiElement, int width, int heigth)
{
   var wbmp = new WriteableBitmap(width, heigth);
   try
   {
      wbmp.Render(uiElement, null);
      wbmp.Invalidate();

      var tileImageName = _liveTileStoreLocation;
      using (var stream = new IsolatedStorageFileStream(tileImageName, FileMode.Create, FileAccess.Write, IsolatedStorageFile.GetUserStoreForApplication()))
      {
         wbmp.SaveJpeg(stream, width, heigth, 0, 100);
         stream.Close();
      }

      uiElement = null;
      wbmp = null;
      GC.Collect();
      return "isostore:" + tileImageName;
   }
   catch (Exception exception)
   {
      // ...
   }
   return null;
}

我做了一些测试,问题是:这个方法泄漏内存,但我不知道 为什么/在哪里?!

我还做了一些测试 - 在第一次进入这个方法之前:

Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage
7.249.920 Bytes

这没关系,因为附加了调试器,它使用了大约2 MB的内存。

再做一些此方法的运行(在调试器中再次设置为run方法):

Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage
8851456  long +  40960
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage
8892416  long + 245760
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage
9138176  long + 143360
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage
9281536  long + 151552
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage
9433088  long + 143360
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage
9576448  long + 139264
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage
9715712  long + 139264
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage
9859072  long + 143360
Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage
10006528 long + 147456

因此,此方法使用的内存会增加。

但为什么呢? 在我看来,没有任何参考文献可以防止收集对象。

于2013年5月4日更新

您好,

谢谢你的所有答案! 正如所建议的,我减少了代码+最终能够在几行代码中重现问题。

void Main()
{
   for (int i = 0; i < 100; i++)
   {
      CreateImage();
   }
}

private void CreateImage()
{
   var rectangle = CreateRectangle();
   var writeableBitmap = new WriteableBitmap(rectangle, rectangle.RenderTransform);

   rectangle = null;
   writeableBitmap = null;
   GC.Collect();
}

private Rectangle CreateRectangle()
{
   var solidColorBrush = new SolidColorBrush(Colors.Blue);
   var rectangle = new Rectangle
   {
   Width = 1000,
   Height = 1000,
   Fill = solidColorBrush  // !!! THIS causes that the image writeableBitmap never gets garbage collected
   };

   return rectangle;
}

启动App后:ApplicationCurrentMemoryUsage:“11 681 792 Bytes”

1迭代 - ApplicationCurrentMemoryUsage:“28 090 368 Bytes”

5迭代 - ApplicationCurrentMemoryUsage:“77 111 296字节”

20次迭代 - ApplicationCurrentMemoryUsage:“260 378 624 Bytes”

23次迭代后:内存异常。 Ln。:var writeableBitmap = new WriteableBitmap(rectangle,rectangle.RenderTransform);

只有通过注释掉“Fill = solidColorBrush”这一行,CreateImage()方法被调用了100次而没有任何问题 - 在第100次迭代之后,内存使用率约为“16 064 512字节”。

所以看来问题就是刷!! 当用于填充UI元素,稍后此UI元素在可写位图上呈现时,位图永远不会被垃圾收集。

当然,这在我看来毫无意义。 刷子超出了范围,所以它也应该被垃圾收集! (在使用刷子后将其设置为null并没有改变任何东西)

我的许多UI元素都使用画笔来填充,所以我不能简单地删除画笔的用法。 你怎么看待这个问题?

5 个答案:

答案 0 :(得分:10)

问题是rectangle.RenderTransform是一个对象的实例,如果你将writableBitmap设置为null,那么rectangle.RenderTransform对象仍然存活并将矩形保存在内存中...所以解决方法是将代码编辑为如下:

private void CreateImage()
{
   var rectangle = CreateRectangle();
   var writeableBitmap = new WriteableBitmap(rectangle, rectangle.RenderTransform);

   rectangle.RenderTransform = null; //and your memory would be happy ;)
   rectangle = null;
   writeableBitmap = null;
   GC.Collect();
}

查看内存截图...

之前:

memory without setting rectangle.RenderTransform to null

之后:

memory with setting rectangle.RenderTransform to null

答案 1 :(得分:0)

你最后在try catch中应该是wbmp = null才能启动。

您正在渲染它,这意味着您将该对象附加到外部(函数)列表。因此,没有任何数量的GC.Collect会实际收集它,因为它仍在“正在播放”。

将uielement源设置为null,这可能会消除附加的引用,为什么GC忽略它。

答案 2 :(得分:0)

您可以将可写位图图像宽度X高度的大小限制为更小的尺寸,因为当您增加其大小时需要更多内存,因此您可以先限制其初始大小,然后另存为原始图块大小的jpeg

答案 3 :(得分:0)

在taskagent中将uielement转换为writeablebitmap时会出现相同的异常。刚试了这么多次。我找到了解决办法。

wbmp.SaveJpeg(stream, width, heigth, 0, 60);

将uielement保存为流时,可以将质量更改为60。它可以减少内存使用量。你可以尝试一下。

答案 4 :(得分:-1)

尝试放置这部分:

      uiElement = null;
      wbmp = null;
      GC.Collect();

最终子句;