c# - 如何加快向 ListView 添加项目的速度?

标签 c# winforms performance listview

我正在向 WinForms ListView 添加几千个(例如 53,709)项。

尝试 1:13,870 毫秒

foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);
}

这运行得非常糟糕。显而易见的第一个修复方法是调用 BeginUpdate/EndUpdate

尝试 2:3,106 毫秒

listView.BeginUpdate();
foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);
}
listView.EndUpdate();

这样更好,但仍然太慢了一个数量级。让我们将 ListViewItems 的创建与添加 ListViewItems 分开,以便我们找到真正的罪魁祸首:

尝试 3:2,631 毫秒

var items = new List<ListViewItem>();
foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   items.Add(item);
}

stopwatch.Start();

listView.BeginUpdate();
    foreach (ListViewItem item in items)
        listView.Items.Add(item));
listView.EndUpdate();

stopwatch.Stop()

真正的瓶颈是添加项目。让我们尝试将其转换为 AddRange 而不是 foreach

尝试 4:2,182 毫秒

listView.BeginUpdate();
listView.Items.AddRange(items.ToArray());
listView.EndUpdate();

好一点。让我们确保瓶颈不在 ToArray()

尝试 5: 2,132 毫秒

ListViewItem[] arr = items.ToArray();

stopwatch.Start();

listView.BeginUpdate();
listView.Items.AddRange(arr);
listView.EndUpdate();

stopwatch.Stop();

限制似乎是将项目添加到 ListView 。也许是 AddRange 的另一个重载,我们在其中添加 ListView.ListViewItemCollection 而不是数组

尝试 6: 2,141 毫秒

listView.BeginUpdate();
ListView.ListViewItemCollection lvic = new ListView.ListViewItemCollection(listView);
lvic.AddRange(arr);
listView.EndUpdate();

那也好不了。

现在是时候拉伸(stretch)一下了:

  • 第 1 步 - 确保没有列设置为“自动宽度”:

    enter image description here

    检查

  • 第 2 步 - 确保 ListView 不会在我每次添加项目时尝试对项目进行排序:

    enter image description here

    检查

  • 第 3 步 - 询问 stackoverflow:

    enter image description here

    检查

注意:显然这个ListView不是虚拟模式;因为您没有/不能将项目“添加”到虚拟 ListView (您设置了 VirtualListSize)。幸运的是,我的问题不是关于虚拟模式下的 ListView 。

有什么我遗漏的可能导致向 ListView 添加项目如此缓慢的原因吗?


奖金聊天

我知道 Windows ListView 类可以做得更好,因为我可以编写在 394 毫秒 内完成它的代码:

ListView1.Items.BeginUpdate;
for i := 1 to 53709 do
   ListView1.Items.Add();
ListView1.Items.EndUpdate;

与等效的 C# 代码 1,349 ms 相比:

listView.BeginUpdate();
for (int i = 1; i <= 53709; i++)
   listView.Items.Add(new ListViewItem());
listView.EndUpdate();

快了一个数量级。

我缺少 WinForms ListView 包装器的什么属性?

最佳答案

我查看了 ListView 的源代码,我注意到一些事情可能会使性能降低 4 倍左右,如您所见:

在 ListView.cs 中,ListViewItemsCollection.AddRange 调用 ListViewNativeItemCollection.AddRange,这是我开始审计的地方

ListViewNativeItemCollection.AddRange(从第 18120 行开始)两次遍历整个值集合,一次收集所有选中的项目,另一次在 InsertItems 之后“恢复”它们> 被调用(它们都受到针对 owner.IsHandleCreated 的检查的保护,所有者是 ListView)然后调用 BeginUpdate

ListView.InsertItems(来自第 12952 行),第一次调用,对整个列表进行另一次遍历,然后调用 ArrayList.AddRange(可能是那里的另一遍),然后是另一遍。导致

ListView.InsertItems(从第 12952 行开始),第二次调用(通过 EndUpdate)又一次通过,将它们添加到 HashTable , 而 Debug.Assert(!listItemsTable.ContainsKey(ItemId)) 会在 Debug模式下进一步降低它的速度。如果未创建句柄,它会将项目添加到 ArrayListlistItemsArray如果 (IsHandleCreated),则它会调用

ListView.InsertItemsNative(来自第 3848 行)最终通过列表,实际添加到 native ListView 。 Debug.Assert(this.Items.Contains(li) 还会降低 Debug模式下的性能。

因此,在将项目实际插入 native ListView 之前,.net 控件中的整个项目列表有很多额外的传递。一些通行证通过检查正在创建的 Handle 来保护,因此如果您可以在创建 handle 之前添加项目,它可能会节省您一些时间。 OnHandleCreated 方法采用 listItemsArray 并直接调用 InsertItemsNative,无需所有额外的麻烦。

您可以阅读 reference source 中的 ListView 代码你自己看看,也许我漏掉了什么。

In the March 2006 issue of MSDN Magazine有一篇名为 Winning Forms:提升 Windows Forms 应用程序性能的实用技巧 的文章。

本文包含改进 ListViews 性能的提示等。这似乎表明在创建句柄之前添加项目更快,但在呈现控件时你将付出代价。也许应用评论中提到的渲染优化并在创建句柄之前添加项目将获得两全其美。

编辑:以多种方式测试了这个假设,虽然在创建句柄之前添加项目非常快,但在创建句柄时速度却呈指数级下降。我试图欺骗它来创建句柄,然后以某种方式让它调用 InsertItemsNative 而无需经过所有额外的传递,但是我被挫败了。我认为唯一可能的事情是在 c++ 项目中创建 Win32 ListView,用项目填充它,并使用 Hook 捕获 ListView 在创建其句柄时发送的 CreateWindow 消息,并传回对 win32 的引用ListView 而不是新窗口..但谁知道这会产生什么副作用...Win32 专家需要大声说出这个疯狂的想法:)

关于c# - 如何加快向 ListView 添加项目的速度?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9008310/

相关文章:

java - StringBuilder - 使用串联与在循环中使用追加

c# - C#中 "this"是什么意思

c# - 尝试后终于没有被调用

c# - 更改已开发的 winform 应用程序的默认大小

c# - 为什么粘贴到 DataGridView 中的代码如此慢?

python - Pandas:在整个数据框架上应用复杂函数的最有效方式

c# - 检索Windows设备的DPI

c# - 在 C# 中将位从 ulong 复制到 long

c# - 添加和读取多个文本框

javascript - CSS/JS 动态缩小? (表现)