10

概要

UI(グループ化されたデータグリッド)にバインドしたい、急速に変化する大規模なデータセットがあります。変更は 2 つのレベルにあります。

  • コレクションからアイテムが頻繁に追加または削除されます (片道 500 秒)
  • 各アイテムには 4 つのプロパティがあり、その有効期間内に最大 5 回変更されます

データの特徴は以下の通りです。

  • コレクションには約 5000 個のアイテムがあります
  • アイテムは、1 秒以内に追加され、5 つのプロパティが変更されてから削除される場合があります。
  • アイテムは、しばらくの間、暫定的な状態のままになることもあり、ユーザーに表示する必要があります。

私が問題を抱えている重要な要件。

  • ユーザーは、オブジェクトの任意のプロパティでデータセットを並べ替えることができる必要があります

やりたいこと;

  • UI のみをN秒ごとに更新する
  • 関連する NotifyPropertyChangedEvents のみを発生させる

項目 1 に、間隔内で A -> B -> C -> D から移動するプロパティ State がある場合、A->D という 1 つの「状態」変更イベントのみを発生させる必要があります。

ユーザーが 1 秒間に何千回も UI を更新する必要がないことに感謝します。項目が追加され、その状態が変更され、UI 更新間の N 秒のウィンドウ内ですべて削除された場合、DataGrid にヒットすることはありません。

データグリッド

DataGrid は、データを表示するために使用しているコンポーネントです。動的なグループ化を簡単に提供するため、現在 XCeed DataGrid を使用しています。私はそれに感情的に投資していません.いくつかの動的なグループ化オプションを提供できれば、株式のDataGridは問題ありません(頻繁に変更されるプロパティが含まれます)。

私のシステムの現在のボトルネックは、アイテムのプロパティが変更されたときに再ソートするのにかかる時間です

これにより、YourKit Profiler で CPU の 98% が使用されます。

質問を表現する別の方法

最初は同一であった 2 つの BindingList / ObservableCollection インスタンスが与えられたが、最初のリストには一連の追加の更新 (リッスン可能) があり、1 つのリストを別のリストに変えるための最小限の変更セットを生成します。

外部読み取り

私が必要としているのは、George Tryfonas によるこのArrayMonitorに相当するものですが、アイテムの追加と削除をサポートするように一般化されています (それらは移動されません)。

NB より良い要約を考えることができれば、誰かが質問のタイトルを編集してくれたことを本当に感謝しています。

編集 - 私の解決策

XCeed グリッドはセルをグリッド内の項目に直接バインドしますが、並べ替えとグループ化の機能は BindingList で発生した ListChangedEvents によって駆動されます。これは少し直感に反しており、グループの前に行が更新されるため、以下の MontioredBindingList を除外しました。

代わりに、アイテム自体をラップし、Property changed イベントをキャッチして、Daniel が提案したように HashSet に格納します。これは私にとってはうまくいきます。定期的にアイテムを繰り返し、変更を通知するように依頼します。

MonitoredBindingList.cs

これは、更新通知をポーリングできるバインディング リストでの私の試みです。最終的には役に立たなかったので、いくつかのバグがある可能性があります。

追加/削除イベントのキューを作成し、リストを介して変更を追跡します。ChangeList は基礎となるリストと同じ順序であるため、追加/削除操作を通知した後、適切なインデックスに対して変更を発生させることができます。

/// <summary>
///  A binding list which allows change events to be polled rather than pushed.
/// </summary>
[Serializable]

public class MonitoredBindingList<T> : BindingList<T>
{
    private readonly object publishingLock = new object();

    private readonly Queue<ListChangedEventArgs> addRemoveQueue;
    private readonly LinkedList<HashSet<PropertyDescriptor>> changeList;
    private readonly Dictionary<int, LinkedListNode<HashSet<PropertyDescriptor>>> changeListDict;

    public MonitoredBindingList()
    {
        this.addRemoveQueue = new Queue<ListChangedEventArgs>();
        this.changeList = new LinkedList<HashSet<PropertyDescriptor>>();
        this.changeListDict = new Dictionary<int, LinkedListNode<HashSet<PropertyDescriptor>>>();
    }

    protected override void OnListChanged(ListChangedEventArgs e)
    {
        lock (publishingLock)
        {
            switch (e.ListChangedType)
            {
                case ListChangedType.ItemAdded:
                    if (e.NewIndex != Count - 1)
                        throw new ApplicationException("Items may only be added to the end of the list");

                    // Queue this event for notification
                    addRemoveQueue.Enqueue(e);

                    // Add an empty change node for the new entry
                    changeListDict[e.NewIndex] = changeList.AddLast(new HashSet<PropertyDescriptor>());
                    break;

                case ListChangedType.ItemDeleted:
                    addRemoveQueue.Enqueue(e);

                    // Remove all changes for this item
                    changeList.Remove(changeListDict[e.NewIndex]);
                    for (int i = e.NewIndex; i < Count; i++)
                    {
                        changeListDict[i] = changeListDict[i + 1];
                    }

                    if (Count > 0)
                        changeListDict.Remove(Count);
                    break;

                case ListChangedType.ItemChanged:
                    changeListDict[e.NewIndex].Value.Add(e.PropertyDescriptor);
                    break;
                default:
                    base.OnListChanged(e);
                    break;
            }
        }
    }

    public void PublishChanges()
    {
        lock (publishingLock)
            Publish();
    }

    internal void Publish()
    {
        while(addRemoveQueue.Count != 0)
        {
            base.OnListChanged(addRemoveQueue.Dequeue());
        }

        // The order of the entries in the changeList matches that of the items in 'this'
        int i = 0;
        foreach (var changesForItem in changeList)
        {
            foreach (var pd in changesForItem)
            {
                var lc = new ListChangedEventArgs(ListChangedType.ItemChanged, i, pd);
                base.OnListChanged(lc);
            }
            i++;
        }
    }
}
4

1 に答える 1

5

ここでは2つのことについて話します。

  1. コレクションへの変更。これにより、イベントINotifyCollectionChanged.CollectionChangedが発生します
  2. アイテムのプロパティへの変更。これにより、イベントINotifyPropertyChanged.PropertyChangedが発生します

インターフェイスINotifyCollectionChangedは、カスタムコレクションによって実装する必要があります。インターフェイスINotifyPropertyChangedはアイテムによって実装される必要があります。さらに、このPropertyChangedイベントは、アイテムで変更されたプロパティのみを通知し、以前の値は通知しません。
つまり、アイテムには次のような実装が必要です。

  • N秒ごとに実行されるタイマーがあります
  • HashSet<string>変更されたすべてのプロパティの名前を含むを作成します。セットであるため、各プロパティを含めることができるのは1回または0回のみです。
  • プロパティが変更されたときに、その名前がまだハッシュセットに含まれていない場合は、その名前をハッシュセットに追加します。
  • タイマーが経過したら、PropertyChangedハッシュセット内のすべてのプロパティのイベントを発生させ、後でクリアします。

コレクションにも同様の実装があります。ただし、タイマーイベントの間に追加および削除されたアイテムを考慮する必要があるため、少し難しくなります。つまり、アイテムが追加されると、それをハッシュセット「addedItems」に追加します。アイテムが削除された場合、それが「addedItems」にまだ含まれていない場合は、「removedItems」ハッシュセットに追加します。すでに「addedItems」にある場合は、そこから削除します。私はあなたが絵を手に入れると思います。

関心の分離と単一責任の原則を順守するには、アイテムをINotifyPropertyChangedデフォルトの方法で実装し、イベントの統合を行うラッパーを作成することをお勧めします。これには、アイテムがそこに属していないコードで乱雑にならないという利点があり、このラッパーを汎用にして、を実装するすべてのクラスで使用できますINotifyPropertyChanged
コレクションについても同じことが言えます。実装するすべてのコレクションの汎用ラッパーを作成し、INotifyCollectionChangedラッパーにイベントの統合を実行させることができます。

于 2011-03-15T10:20:06.917 に答える