【问题标题】:WriteableBitmap Memory Leak?WriteableBitmap 内存泄漏?
【发布时间】:2013-01-20 13:48:02
【问题描述】:

我正在使用下面的代码来创建一个基于 UI 元素的动态磁贴。 它在WriteableBitmap 上渲染uiElement,保存位图+ 返回文件名。 此方法在 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 元素都使用画笔进行填充,因此我不能简单地删除画笔的使用。 您如何看待这个问题?

【问题讨论】:

  • 您应该注意,通常没有理由直接调用 GC.Collect()。它可能有害、冻结您的应用程序并影响性能。
  • 确实,这更像是某种“拼命尝试”,实际上它什么也不做,也不影响内存消耗。
  • 想知道如果完成画布会发生什么?
  • 画布仅用于在 WriteableBitmap 上渲染,在此方法后不再使用,也不再显示在 UI 中,因为这是一个后台任务。但你是对的,将其设置为 null 并不是“好的做法”,但它对行为完全没有影响。
  • 您能否通过注释掉代码行然后检查泄漏来进一步缩小泄漏的原因。首先,只是渲染和无效但不写入存储?漏水还在吗?然后,只需将 jpeg 写入隔离存储,无需先渲染和失效。我猜一些系统进程正在持有对 WriteableBitmap 的前缓冲区或后缓冲区的引用......

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


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

    查看内存截图...

    之前:

    之后:

    【讨论】:

    • 你是如何得到这些内存分析的?我正在使用 VS 2013 Preview / Ultimate。
    • 可以通过以下方式打开这个透视图:DEBUG > Start Performance Analysis (Alt+F2)
    【解决方案3】:

    试着把这部分:

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

    finally子句;

    【讨论】:

    • 我测试了这个,但它并没有解决问题,请查看更新的帖子。
    • -1 将事物设置为null 只是货物崇拜编程。它没有任何用处,并且在某些情况下实际上会干扰垃圾收集。此外,在生产代码中对GC.Collect() 的调用是一个巨大的危险信号,程序员不知道他们在做什么,并且正在与垃圾收集器而不是与它一起工作。
    • @CodyGray 我只是说如果您正确阅读原始问题,我不建议编写 nulls 和 GC.Collect(),它是原始代码的一部分,我只建议移动它最后阻止。更重要的是,接受的答案具有相同的空值和 GC.Collect(),不确定,你为什么专门减去我,你甚至想从我这里得到什么。
    【解决方案4】:

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

    【讨论】:

    • 但这会放大图像,导致图像质量不佳
    【解决方案5】:

    try catch 中的 finally 应该是 wbmp = null 才能开始。

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

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

    【讨论】:

    • 谢谢,我都试过了,但都没有帮助 - 请查看更新后的帖子 - 问题仍然存在。
    猜你喜欢
    • 1970-01-01
    • 2013-08-23
    • 1970-01-01
    • 1970-01-01
    • 2011-10-08
    • 2011-10-31
    • 2019-08-10
    • 2013-06-24
    • 2011-03-22
    相关资源
    最近更新 更多