快速执行和线程安全的可观察收集
ObservableCollection
会为每个对其执行的操作提出通知。 首先,他们没有批量添加或删除调用,其次他们不是线程安全的。
这不会让他们变慢吗? 不能我们有一个更快的select? 有人说ICollectionView
缠绕在ObservableCollection
上的速度很快吗? 这个说法多么真实 ?
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); } } } } } }
此外,位于ObservableCollection
上方的ICollectionView
主动知道这些更改,并与其他任何源列表相比执行过滤,分组和sorting相对较快。
对于更快的数据更新,可观察的集合可能不是一个完美的答案,但是他们的工作相当好。
这是我编写的一些解决scheme的汇编。 收集的想法改变从第一个答案采取invokation。
也似乎“重置”操作应与主线程同步,否则奇怪的事情发生在CollectionView和CollectionViewSource。
我想这是因为在“重置”处理程序试图立即读取收集内容,他们应该已经到位。 如果你做“重置”asynchronous,并立即添加一些项目也asynchronous比新添加的项目可能会被添加两次。
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(); } } } }
我不能添加评论,因为我还不够酷,但分享我遇到的这个问题可能值得张贴,即使这不是一个真正的答案。 由于BeginInvoke,我一直使用这个FastObservableCollection得到一个“索引超出范围”exception。 显然,被调用的处理程序之前被通知的更改可以被撤消,所以为了解决这个问题,我将下面的代码作为从OnCollectionChanged方法调用的BeginInvoke的第四个parameter passing(与使用事件参数one相反):
dispatcherObject.Dispatcher.BeginInvoke (DispatcherPriority.DataBind, handler, this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
而不是这个:
dispatcherObject.Dispatcher.BeginInvoke (DispatcherPriority.DataBind, handler, this, e);
这固定了我遇到的“索引超出范围”的问题。 这里有一个更详细的解释/代码snpipet: 我在哪里得到一个线程安全的CollectionView?
一个例子是创build一个同步的Observable列表:
newSeries = new XYChart.Series<>(); ObservableList<XYChart.Data<Number, Number>> listaSerie; listaSerie = FXCollections.synchronizedObservableList(FXCollections.observableList(new ArrayList<XYChart.Data<Number, Number>>())); newSeries.setData(listaSerie);