缓慢释放用户控件
本文关键字:控件 用户 释放 缓慢 | 更新日期: 2023-09-27 18:35:07
我的窗体上有两个控件:一个包含工作人员列表的列表框和一个充当容器的面板,用于显示有关其工作的详细信息(卡片)。当用户单击工作人员的姓名时,我会在面板上显示卡片。卡片是一个用户控件,具有一些相当简单的 UI(2 个分组框、3 个文本框和多个标签)和简单的逻辑(设置标签的前色)。
卡片是在运行时创建的。以前的卡片将从面板中移除,新卡片被添加 - 每个工作人员的卡片数量为 1 到 4。这里变得有趣。一切正常,直到大约第五次点击工人。似乎 GC 启动了,旧卡(以前删除的卡数)大约需要两秒钟(0.3s x 以前移除的卡数)才能被处理并显示新卡。如果以前在工人之间移动效果很好,那么在这一点上它会变得非常缓慢。经过一番探索,我已经找到了问题所在Dispose
我的使用控制方法。呼叫base.Dispose()
大约需要 0.3 秒。
这是我的代码:
private void ShowCards(List<Work> workItems) {
var y = 5;
panelControl1.SuspendLayout();
panelControl1.Controls.Clear();
foreach (var work in workItems) {
var card = new Components.WorkDisplayControl(work);
card.Top = y;
card.Left = 10;
y += card.Height + 5;
panelControl1.Controls.Add(card);
}
panelControl1.ResumeLayout(true);
Application.DoEvents();
}
到目前为止我尝试过:
- 隐藏卡片而不是处理 - 移动时工作速度更快工人之间,但在关闭表单时支付罚款
- 隐藏卡片并有一个单独的线程来处理它们 - 没有变化
- 添加 10 张卡并立即处理它们进行测试 - 慢
- 通过添加 10 张卡并立即在构造函数中处理它们进行测试 - 快速!
- 将 DevExpress 控件替换为"正常" – 无变化
- 手动处理旧卡,而不是在更换工人时删除它们 - 工人之间的每个动作都会变慢:
for (var ix = panelControl1.Controls.Count - 1; ix >= 0; --ix) { panelControl1.Controls[ix].Dispose();}
- 分析它 - 这就是我在
Dispose
中发现问题的方式.我可以追溯到Control.DestroyHandle
- 在我的控制方法中调用
Controls.Clear()
Dispose
- 超级奇怪的行为和异常 - 从我的用户控件中删除了所有控件 - 有点快,但仍然很慢
- 删除和添加卡片时隐藏
panelControl1
- 无变化 - 关闭背景 GC - 无变化
- 添加带有
AddRange
的卡片
由于从构造函数调用时相同的功能运行速度很快,因此我认为原因一定在(控制)句柄中的某个地方。
我只是找不到这种奇怪行为的原因。我将不胜感激任何想法...
更新:在研究GC和Control.Dispose之间的联系时,我发现了这个很好的答案
在[DevExpress的支持失败]之后,我做了一些测试和使用代码,最终找到了解决方案。
诀窍是在处置之前清除UserControl
上的控件。
可以在 UC 上修改Dispose
方法(此解决方案在某些情况下有效,但并非在所有情况下都有效)或隐藏 UC,而不是将其从面板中删除并更改其Controls
解决方案 1:
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
Controls.Clear(); // <--- Add this line
base.Dispose(disposing);
}
解决方案 2:
向 UC 添加新方法:
public void ClearControls() {
Controls.Clear();
}
在我的原始问题中替换此行
panelControl1.Controls.Clear();
有了这个:
for (var ii = panelControl1.Controls.Count - 1; ii >= 0; --ii) {
var wdc = panelControl1.Controls[ii] as Components.WorkDisplayControl;
wdc.Visible = false;
wdc.ClearControls();
}
它的工作速度(至少)快 20 倍,这已经足够好了。
问题的原因与 DevExpress 或标准控件无关。但是,它与创建和销毁控制句柄有关。为了改进卡的实现,请尽可能避免这些操作。我建议你对你的卡片使用缓存:
void ShowCards(List<Work> workItems) {
cardsPanel.SuspendLayout();
CacheCards(cardsPanel.Controls);
int y = 5;
foreach(var work in workItems) {
var card = GetCardFromCache(work);
card.Top = y;
card.Left = 10;
y += card.Height + 5;
cardsPanel.Controls.Add(card);
}
cardsPanel.ResumeLayout(true);
}
//
Stack<WorkDisplayControl> cache;
void CacheCards(Control.ControlCollection controls) {
if(cache == null)
cache = new Stack<WorkDisplayControl>();
foreach(WorkDisplayControl wdc in controls)
cache.Push(wdc);
controls.Clear();
}
WorkDisplayControl GetCardFromCache(Work data) {
WorkDisplayControl result = (cache.Count > 0) ?
cache.Pop() : new WorkDisplayControl();
result.InitData(data);
return result;
}
优化卡的下一步是减少使用的控制手柄总数。由于您使用的是 DevExpress 控件,因此最适合您的选择 XtraLayoutControl。使用 XtraLayoutControl 可以显著减少控制手柄的总数。它只为您描述的布局创建 4 个手柄(3 个编辑器,在几个分组框中带有标签),而不是在使用标准控件时创建 8 个手柄。XtraLayoutControl 不会为编辑器的标签、组、选项卡创建句柄。另请查看XtraGrid LayoutView - 它提供了使用网格的数据绑定架构和卡的布局虚拟化的好处,而无需任何额外的编码。