我正在向 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 步 - 确保没有列设置为“自动宽度”:
检查
第 2 步 - 确保 ListView 不会在我每次添加项目时尝试对项目进行排序:
检查
第 3 步 - 询问 stackoverflow:
检查
注意:显然这个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模式下进一步降低它的速度。如果未创建句柄,它会将项目添加到 ArrayList
、listItemsArray
但如果 (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/