【问题标题】:C# Huge memory leak when scrolling using OwnerDraw/DrawItem in ListViewC# 在 ListView 中使用 OwnerDraw/DrawItem 滚动时出现巨大的内存泄漏
【发布时间】:2017-02-12 03:57:27
【问题描述】:

我最近开始分析我的 WinForms 应用程序,并注意到在上下滚动 ListView 时出现巨大的内存泄漏。

如果我不通过 .OwnerDraw 和 DrawItem 进行自己的自定义渲染,则不会发生此泄漏。

我的 ListView 由一个图像、文本和一个绘制在选定对象顶部的矩形组成。我错过了一些清理工作吗?似乎它在无休止地重绘东西,这就是内存使用量堆积的原因。

如果我连续上下滚动列表,它会在 10 秒内攀升至超过 100mb 的内存使用量。如果我不使用 OwnerDraw,内存使用将保持一致。

根据我见过的例子,我的用法似乎是有效的。

感谢您的帮助。

// ## Inside form load ## //
listView2.OwnerDraw = true;
listView2.DrawItem += new DrawListViewItemEventHandler(listView2_DrawItem);

// ## How the items are added to the ListView (in form load) ## //

// Create an image list
ImageList imageList = new ImageList();
imageList.ImageSize = new Size(256, 178);
imageList.ColorDepth = ColorDepth.Depth32Bit;
// Assign the image list to the listView
listView2.LargeImageList = imageList;
// Create an array of 20 listView items
ListViewItem[] items = new ListViewItem[20];

// Create 20 items
for (int i = 0; i < 20; i++)
{
    // Add the image for this item to the image list
    imageList.Images.Add(Image.FromFile("myFile2.jpg"));
    // Create the item
    items[i] = new ListViewItem("Item");
    items[i].ImageIndex = i;
    // Add the item to the listView
    listView2.Items.Add(icons[i]);
}

// ## Draw item handler ## //
private void listView2_DrawItem(object sender, DrawListViewItemEventArgs e)
{   
    Rectangle rect3 = new Rectangle(e.Bounds.Left + 10, e.Bounds.Top + 10, 256, 178);
    Rectangle rect4 = new Rectangle(rect3.Left, e.Bounds.Top, rect3.Width, e.Bounds.Height + 14);
    TextFormatFlags flags = TextFormatFlags.HorizontalCenter |
            TextFormatFlags.Bottom | TextFormatFlags.WordBreak;

    // Draw the image for this item
    e.Graphics.DrawImage(e.Item.ImageList.Images[e.ItemIndex], rect3);

    // Draw the text and the surrounding rectangle.
    TextRenderer.DrawText(e.Graphics, e.Item.Text, new Font("Arial", 12), rect4, Color.Black, flags);

    if (e.Item.Selected)
    {
        int width = e.Item.ImageList.Images[e.ItemIndex].Width;
        int height = e.Item.ImageList.Images[e.ItemIndex].Height;
        Brush brush = new SolidBrush(Color.FromArgb(180, 1, 1, 1));
        e.Graphics.FillRectangle(brush, rect3);

        TextFormatFlags flags2 = TextFormatFlags.HorizontalCenter |
            TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak;

        // Draw the text
        TextRenderer.DrawText(e.Graphics, e.Item.Text, new Font("Arial", 12), rect3, Color.White, flags2);
    }
}

【问题讨论】:

  • 是什么让你认为你实际上是在泄漏内存(除了字体和画笔)? GC 只在需要时启动;所以内存使用量经常会堆积到那时..
  • 我现在要关闭任务管理器。如果我不断上下滚动,它可以在 10 秒内使用 100mb 的内存。我相信它会泄漏,因为它不会在使用默认绘图时发生,只有在我控制绘图时才会发生。我还没有尝试继续超过 100+ MB 以查看它是否会达到上限。我担心由于标准表单绘制根本没有泄漏(内存使用没有随着默认绘制而攀升)
  • 听起来很正常。在具有 GB RAM 且没有竞争程序的机器中,100MB 不算什么。添加缺少的 using 子句并尝试使其真正达到更大的“泄漏”..!
  • 即使只有对图像列表项的绘制调用是那里的唯一代码,也会发生 100mb。我知道在上下文中使用率似乎并不高,但是为什么在使用默认绘图代码(不使用所有者绘图)时它不会爬升,但使用呢?这对我来说没有加起来
  • @TaW 原来你是对的。它一直攀升到大约 1GB 左右,然后垃圾收集器开始工作。猜猜这毕竟是正常的。我是 C# 及其内存管理的新手,所以我只是假设它最初是泄漏的。谢谢

标签: c# winforms listview


【解决方案1】:

您需要同时处理新的 Font("Arial", 12) 对象和 Brush。

您泄漏的不仅仅是内存,您还泄漏了 GDI 对象。

您可以在表单加载时创建一次字体并在其关闭时将其释放(更好的性能),或者在需要它的 DrawItem 块中使用 using 块创建和释放它(速度较慢,但​​代码更简单)

【讨论】:

  • 感谢您的信息!问题是,主要泄漏是在绘制图像列表项的调用中。如果其他所有内容都被注释掉,除了那个和 rect3 对象,我可以通过在大约 10 秒内连续上下滚动来泄漏 100mb 的内存
【解决方案2】:

解决方案是使用:

this.listView2.LargeImageList.Draw(e.Graphics, gameItemRect.Left, gameItemRect.Top, gameItemRect.Width, gameItemRect.Height, e.ItemIndex);

这使用内部句柄而不是创建图像的副本。 正如@Tim 所述,笔刷和字体还需要在代码块之外进行预初始化,以防止为每个绘图事件重新创建它们和泄漏内存。然而,主要的内存泄漏是由于图像绘制调用。

现在内存使用与不使用 OwnerDraw 的情况一致。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-15
    • 2010-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多