1

フォームには 2 つのコントロールがあります。ワーカーのリストを含むリストボックスと、作業に関する詳細 (カード) を表示するためのコンテナーとして機能するパネルです。ユーザーがワーカーの名前をクリックすると、パネルにカードが表示されます。カードは、かなり単純な UI (2 つのグループ ボックス、3 つのテキスト ボックス、およびいくつかのラベル) と単純なロジック (ラベルの前色の設定) を備えたユーザー コントロールです。

カードは実行時に作成されます。以前のカードがパネルから削除され、新しいカードが追加されます。ワーカーごとのカードの数は 1 から 4 です。ここで興味深いことがわかります。約まですべてが正常に機能します。ワーカーの 5 回目のクリック。GC が開始され、古いカード (以前に削除された) が破棄されて新しいカードが表示されるまでに約 2 秒 (以前に削除されたカードの数の 0.3sx) かかるようです。以前はワーカー間の移動がうまく機能していたとしても、その時点で非常に遅くなります。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の間の接続を調査しているときに、この優れた答えを見つけました

4

2 に答える 2

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 倍高速に動作しますが、これで十分です。

于 2012-05-24T15:15:37.007 に答える
0

この問題の原因は、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 を使用すると、コントロール ハンドルの総数を大幅に減らすことができます。標準コントロールが使用されている場合、8 つのハンドルではなく、説明したレイアウト用に 4 つのハンドル (いくつかのグループ ボックス内にラベルを持つ 3 つのエディター) のみが作成されます。XtraLayoutControl は、エディターのラベル、グループ、タブのハンドルを作成しません。XtraGrid LayoutViewもご覧ください。追加のコーディングなしで、グリッドのデータバインディング アーキテクチャとカードのレイアウト仮想化を使用する利点が得られます。

于 2012-05-28T04:39:20.457 に答える