ObservableCollection
会在每次对其执行的操作上发出通知。首先,它们没有批量添加或删除调用,其次它们不是线程安全的。
这难道不会使它们变慢吗?难道我们没有更快的替代方案吗?一些人说,包装在ObservableCollection
周围的ICollectionView
是快速的?这种说法有多真实呢?
ObservableCollection
会在每次对其执行的操作上发出通知。首先,它们没有批量添加或删除调用,其次它们不是线程安全的。
这难道不会使它们变慢吗?难道我们没有更快的替代方案吗?一些人说,包装在ObservableCollection
周围的ICollectionView
是快速的?这种说法有多真实呢?
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
之上,可以主动感知变化并执行过滤、分组、排序等操作,相对于其他源列表来说速度更快。
再次强调,可观察集合可能不是更快数据更新的完美解决方案,但它们能够胜任其工作。
AddItems
实现对于大量项目是实用的。对于几百个项目,我甚至不会费心使用AddItems
。但是,当我向集合中添加数千个项目时,我获得的性能提升是显着的。您可以自行测试。1000个新添加的项目和1000个单独的通知的重置会产生很大的差异。此外,WPF项容器在任何情况下都会重用虚拟化的项。因此,即使是Reset
调用,项容器也会重用indi行。 - WPF-itpublic 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();
}
}
}
}
我还不够酷,所以无法添加评论,但是分享我遇到的问题可能值得发布,即使它并不是真正的答案。使用这个FastObservableCollection时,我一直收到“索引超出范围”的异常,因为BeginInvoke的原因。显然,在调用处理程序之前,通知的更改可以被撤消,因此为了解决这个问题,我将以下内容作为从OnCollectionChanged方法调用的BeginInvoke的第四个参数传递(而不是使用事件args):
dispatcherObject.Dispatcher.BeginInvoke
(DispatcherPriority.DataBind, handler, this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
不是这样:
dispatcherObject.Dispatcher.BeginInvoke
(DispatcherPriority.DataBind, handler, this, e);
我遇到了“索引超出范围”的问题,这个解决方案解决了我的问题。以下是更详细的说明/代码片段:我从哪里获取线程安全的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);