Быстрая производительность и потокобезопасная наблюдаемая коллекция
ObservableCollection
s поднимать уведомления для каждого действия, выполненного на них. Во-первых, у них нет массового добавления или удаления вызовов, во-вторых, они не потокобезопасны.
ICollectionView
, обернутый вокруг ObservableCollection
, быстр? Насколько истинно это утверждение.4 ответа:
Приведенный ниже код является очень хорошим примером потокобезопасной, более быстрой наблюдаемой коллекции, и вы можете расширить ее дальше по своему желанию.
ObservableCollection
может быть быстрым, если он хочет . :- )using System.Collections.Specialized; public class FastObservableCollection<T> : ObservableCollection<T> { private readonly object locker = new object(); /// <summary> /// This private variable holds the flag to /// turn on and off the collection changed notification. /// </summary> private bool suspendCollectionChangeNotification; /// <summary> /// Initializes a new instance of the FastObservableCollection class. /// </summary> public FastObservableCollection() : base() { this.suspendCollectionChangeNotification = false; } /// <summary> /// This event is overriden CollectionChanged event of the observable collection. /// </summary> public override event NotifyCollectionChangedEventHandler CollectionChanged; /// <summary> /// This method adds the given generic list of items /// as a range into current collection by casting them as type T. /// It then notifies once after all items are added. /// </summary> /// <param name="items">The source collection.</param> public void AddItems(IList<T> items) { lock(locker) { this.SuspendCollectionChangeNotification(); foreach (var i in items) { InsertItem(Count, i); } this.NotifyChanges(); } } /// <summary> /// Raises collection change event. /// </summary> public void NotifyChanges() { this.ResumeCollectionChangeNotification(); var arg = new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Reset); this.OnCollectionChanged(arg); } /// <summary> /// This method removes the given generic list of items as a range /// into current collection by casting them as type T. /// It then notifies once after all items are removed. /// </summary> /// <param name="items">The source collection.</param> public void RemoveItems(IList<T> items) { lock(locker) { this.SuspendCollectionChangeNotification(); foreach (var i in items) { Remove(i); } this.NotifyChanges(); } } /// <summary> /// Resumes collection changed notification. /// </summary> public void ResumeCollectionChangeNotification() { this.suspendCollectionChangeNotification = false; } /// <summary> /// Suspends collection changed notification. /// </summary> public void SuspendCollectionChangeNotification() { this.suspendCollectionChangeNotification = true; } /// <summary> /// This collection changed event performs thread safe event raising. /// </summary> /// <param name="e">The event argument.</param> protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { // Recommended is to avoid reentry // in collection changed event while collection // is getting changed on other thread. using (BlockReentrancy()) { if (!this.suspendCollectionChangeNotification) { NotifyCollectionChangedEventHandler eventHandler = this.CollectionChanged; if (eventHandler == null) { return; } // Walk thru invocation list. Delegate[] delegates = eventHandler.GetInvocationList(); foreach (NotifyCollectionChangedEventHandler handler in delegates) { // If the subscriber is a DispatcherObject and different thread. DispatcherObject dispatcherObject = handler.Target as DispatcherObject; if (dispatcherObject != null && !dispatcherObject.CheckAccess()) { // Invoke handler in the target dispatcher's thread... // asynchronously for better responsiveness. dispatcherObject.Dispatcher.BeginInvoke (DispatcherPriority.DataBind, handler, this, e); } else { // Execute handler as is. handler(this, e); } } } } } }
Также
ICollectionView
, который сидит надObservableCollection
, активно осведомлен об изменениях и выполняет фильтрацию, группировку, сортировку относительно быстро по сравнению с любым другим исходным списком.Опять же наблюдаемые коллекции не могут быть идеальным ответом для более быстрого обновления данных, но они есть их работа довольно хороша.
Вот компиляция некоторых решений, которые я сделал. Идея сбора изменила призыв, взятый из первого ответа.
Также кажется, что операция "сброс" должна быть синхронной с основным потоком, иначе странные вещи происходят с CollectionView и CollectionViewSource.
Я думаю, что это потому, что при" сбросе " обработчик пытается немедленно прочитать содержимое коллекции, и они уже должны быть на месте. Если вы делаете "сброс" асинхронности и чем сразу же добавить некоторые элементы также асинхронность, чем вновь добавленные элементы могут быть добавлены дважды.
public interface IObservableList<T> : IList<T>, INotifyCollectionChanged { } public class ObservableList<T> : IObservableList<T> { private IList<T> collection = new List<T>(); public event NotifyCollectionChangedEventHandler CollectionChanged; private ReaderWriterLock sync = new ReaderWriterLock(); protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { if (CollectionChanged == null) return; foreach (NotifyCollectionChangedEventHandler handler in CollectionChanged.GetInvocationList()) { // If the subscriber is a DispatcherObject and different thread. var dispatcherObject = handler.Target as DispatcherObject; if (dispatcherObject != null && !dispatcherObject.CheckAccess()) { if ( args.Action == NotifyCollectionChangedAction.Reset ) dispatcherObject.Dispatcher.Invoke (DispatcherPriority.DataBind, handler, this, args); else // Invoke handler in the target dispatcher's thread... // asynchronously for better responsiveness. dispatcherObject.Dispatcher.BeginInvoke (DispatcherPriority.DataBind, handler, this, args); } else { // Execute handler as is. handler(this, args); } } } public ObservableList() { } public void Add(T item) { sync.AcquireWriterLock(Timeout.Infinite); try { collection.Add(item); OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, item)); } finally { sync.ReleaseWriterLock(); } } public void Clear() { sync.AcquireWriterLock(Timeout.Infinite); try { collection.Clear(); OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset)); } finally { sync.ReleaseWriterLock(); } } public bool Contains(T item) { sync.AcquireReaderLock(Timeout.Infinite); try { var result = collection.Contains(item); return result; } finally { sync.ReleaseReaderLock(); } } public void CopyTo(T[] array, int arrayIndex) { sync.AcquireWriterLock(Timeout.Infinite); try { collection.CopyTo(array, arrayIndex); } finally { sync.ReleaseWriterLock(); } } public int Count { get { sync.AcquireReaderLock(Timeout.Infinite); try { return collection.Count; } finally { sync.ReleaseReaderLock(); } } } public bool IsReadOnly { get { return collection.IsReadOnly; } } public bool Remove(T item) { sync.AcquireWriterLock(Timeout.Infinite); try { var index = collection.IndexOf(item); if (index == -1) return false; var result = collection.Remove(item); if (result) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); return result; } finally { sync.ReleaseWriterLock(); } } public IEnumerator<T> GetEnumerator() { return collection.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return collection.GetEnumerator(); } public int IndexOf(T item) { sync.AcquireReaderLock(Timeout.Infinite); try { var result = collection.IndexOf(item); return result; } finally { sync.ReleaseReaderLock(); } } public void Insert(int index, T item) { sync.AcquireWriterLock(Timeout.Infinite); try { collection.Insert(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); } finally { sync.ReleaseWriterLock(); } } public void RemoveAt(int index) { sync.AcquireWriterLock(Timeout.Infinite); try { if (collection.Count == 0 || collection.Count <= index) return; var item = collection[index]; collection.RemoveAt(index); OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Remove, item, index)); } finally { sync.ReleaseWriterLock(); } } public T this[int index] { get { sync.AcquireReaderLock(Timeout.Infinite); try { var result = collection[index]; return result; } finally { sync.ReleaseReaderLock(); } } set { sync.AcquireWriterLock(Timeout.Infinite); try { if (collection.Count == 0 || collection.Count <= index) return; var item = collection[index]; collection[index] = value; OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Replace, value, item, index)); } finally { sync.ReleaseWriterLock(); } } } }
Я не могу добавлять комментарии, потому что я еще недостаточно крут, но поделиться этой проблемой, с которой я столкнулся, вероятно, стоит, даже если это не совсем ответ. Я продолжал получать исключение "Index was out of range", используя эту FastObservableCollection, из-за BeginInvoke. По-видимому, изменения, о которых сообщается, могут быть отменены до вызова обработчика, поэтому для исправления этого я передал следующее в качестве четвертого параметра для BeginInvoke, вызываемого из метода OnCollectionChanged (в отличие от используя событие args one):
dispatcherObject.Dispatcher.BeginInvoke (DispatcherPriority.DataBind, handler, this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
Вместо этого:
dispatcherObject.Dispatcher.BeginInvoke (DispatcherPriority.DataBind, handler, this, e);
Это исправило проблему "индекс был вне диапазона", с которой я столкнулся. Вот более подробное объяснение / код snpipet: где я могу получить потокобезопасный CollectionView?
Пример, где создается синхронизированный наблюдаемый список:
newSeries = new XYChart.Series<>(); ObservableList<XYChart.Data<Number, Number>> listaSerie; listaSerie = FXCollections.synchronizedObservableList(FXCollections.observableList(new ArrayList<XYChart.Data<Number, Number>>())); newSeries.setData(listaSerie);