概要
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++;
}
}
}