ObservableCollection
针对他们执行的每项操作提出通知。首先,他们没有批量添加或删除调用,其次他们不是线程安全的。
这不会让它们变慢吗?我们有更快的选择吗?有人说ICollectionView
缠绕ObservableCollection
的速度很快吗? true 是如何声明的。
答案 0 :(得分:72)
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
也会主动了解这些更改,并且与其他任何源列表相比,执行过滤,分组和排序的速度相对较快。
对于更快的数据更新,再次可观察的集合可能不是完美答案,但他们的工作做得很好。
答案 1 :(得分:4)
以下是我制作的一些解决方案的汇编。收集的想法改变了从第一个答案中获得的启发。
似乎&#34;重置&#34;操作应该与主线程同步,否则CollectionView和CollectionViewSource会发生奇怪的事情。
我认为这是因为&#34;重置&#34; handler尝试立即读取集合内容,它们应该已经存在。 如果你做&#34;重置&#34; async和立即添加一些项目也比新添加的项目异步,可能会添加两次。
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();
}
}
}
}
答案 2 :(得分:2)
我无法添加评论,因为我还不够酷,但是我遇到的这个问题可能值得发布,尽管它并不是真正的答案。由于BeginInvoke,我使用此FastObservableCollection不断获得“索引超出范围”异常。显然,在调用处理程序之前,可以撤消通知的更改,因此为了解决这个问题,我将以下内容作为从OnCollectionChanged方法调用的BeginInvoke的第四个参数传递(而不是使用事件args):
dispatcherObject.Dispatcher.BeginInvoke
(DispatcherPriority.DataBind, handler, this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
而不是:
dispatcherObject.Dispatcher.BeginInvoke
(DispatcherPriority.DataBind, handler, this, e);
这解决了我遇到的“索引超出范围”问题。这是一个更详细的解释/代码snpipet:Where do I get a thread-safe CollectionView?
答案 3 :(得分:0)
创建同步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);