20

我正在使用下面的代码创建一个基于 UI 元素的动态磁贴。uiElement它在 a 上渲染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 内存。

再次运行此方法(在调试器中再次设置回运行方法):

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;
}

启动应用程序后:ApplicationCurrentMemoryUsage:"11 681 792 Bytes"

1 次迭代 - ApplicationCurrentMemoryUsage:“28 090 368 字节”

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

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

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

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

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

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

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

4

5 回答 5

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();
}

看内存截图...

前:

没有将 rectangle.RenderTransform 设置为 null 的内存

后:

将 rectangle.RenderTransform 设置为 null 的内存

于 2013-05-30T13:53:11.123 回答
0

您可以将可写位图图像宽度 X 高度的大小限制为更小的尺寸,因为当您增加其大小时会占用更多内存,因此您可以首先限制其初始大小,然后保存为原始平铺大小的 jpeg

于 2013-02-21T10:59:45.310 回答
0

在 taskagent 中将 uielement 转换为 writeablebitmap 时出现相同的异常。只是尝试了很多次。我发现一个解决方案得到了工作。

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

当您将 uielement 保存到流时,您可以将质量更改为 60。它很好地减少了内存使用。你可以试试。

于 2013-12-13T04:47:03.523 回答
0

你在 try catch 中的 finally 应该是 wbmp = null 来开始。

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

将 uielement 源设置为 null,这可能会摆脱 GC 忽略它的附加引用。

于 2013-02-20T20:45:00.007 回答
-1

试着把这部分:

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

to finally子句;

于 2013-02-24T02:20:17.897 回答