【问题标题】:Slow dispose of UserControl缓慢处理 UserControl
【发布时间】:2012-05-22 09:45:46
【问题描述】:

我的表单上有两个控件:一个带有工作人员列表的列表框和一个面板,该面板用作显示有关其工作的详细信息(卡片)的容器。 当用户点击工人的名字时,我会在面板上显示卡片。卡片是一个用户控件,具有一些相当简单的 UI(2 个组框、3 个文本框和几个标签)和简单的逻辑(设置标签的前景色)。

卡片是在运行时创建的。以前的卡片从面板中移除,新的卡片被添加——每个工人的卡片数量是 1 到 4。这里很有趣。 一切正常,直到大约。第五次点击工人。似乎 GC 开始了,大约需要两秒钟(0.3 秒 x 之前移除的卡的数量)来处理旧卡(之前移除)并显示新卡。如果以前在工人之间移动效果很好,那么它会变得非常缓慢。 经过一番探索,我发现问题出在我 usedcontrol 的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
  • 在我控制的Dispose 方法中调用Controls.Clear() - 超级奇怪的行为和异常
  • 从我的用户控件中删除了所有控件 - 快了一点,但仍然很慢
  • 在移除和添加卡片时隐藏 panelControl1 - 没有变化
  • 关闭后台 GC - 没有变化
  • AddRange添加卡片

由于从构造函数调用相同的功能时可以快速运行,我相信原因一定是在(控制)句柄中的某个地方。

我只是找不到这种奇怪行为的原因。我会很感激任何想法....

更新:在研究 GC 和 Control.Dispose 之间的联系时,我发现 this excellent answer

【问题讨论】:

  • Disposing 与垃圾收集器没有任何关系。您正在以一种非常危险的方式使用 Controls.Clear(),它不会处理控件。运行 Taskmgr.exe,进程选项卡。查看 + 选择列并勾选用户对象和 GDI 对象。确保这些值对于您的应用程序来说是稳定的,并且不会无限制地攀爬。联系 devexpress 以获得更多支持。
  • @HansPassant 感谢您的评论和想法。 USER 和 GDI 对象的数量不会改变。 “危险的方式”是什么意思?我只想将它们从我的容器中移除。

标签: c# .net winforms devexpress


【解决方案1】:

在[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 倍,这已经足够了。

【讨论】:

  • 恐怕这根本不是一个解决方案......这是一种危险的方式,因为它有一个控件句柄泄漏(您应该始终处理所有未使用的控件)。看看我的回答。我相信它会有所帮助。
【解决方案2】:

问题的原因与 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 - 它提供了使用网格的数据绑定架构和卡片布局虚拟化的好处,无需任何额外的编码..

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-24
    • 2010-09-30
    • 2011-04-15
    • 2020-08-23
    相关资源
    最近更新 更多