Ниже приведено улучшение реализации, найденной Джонатаном. Во-первых, он запускает каждый обработчик событий в связанном с ним диспетчере, а не предполагает, что все они находятся в одном диспетчере (UI). Во-вторых, он использует BeginInvoke, чтобы продолжить обработку, пока мы ожидаем, что диспетчер станет доступным. Это значительно ускоряет решение в ситуациях, когда фоновый поток выполняет много обновлений с обработкой между ними. Возможно, более важно, что он преодолевает проблемы, вызванные блокировкой во время ожидания Invoke (взаимные блокировки могут возникать, например, при использовании WCF с ConcurrencyMode.Single).
public class MTObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
if (CollectionChanged != null)
foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
{
DispatcherObject dispObj = nh.Target as DispatcherObject;
if (dispObj != null)
{
Dispatcher dispatcher = dispObj.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => nh.Invoke(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
nh.Invoke(this, e);
}
}
}
Поскольку мы используем BeginInvoke, возможно, что уведомление об изменении отменяется до вызова обработчика. Это обычно приводит к тому, что «индекс выходит за пределы диапазона». генерируется исключение, когда аргументы события проверяются относительно нового (измененного) состояния списка. Чтобы избежать этого, все отложенные события заменяются событиями сброса. В некоторых случаях это может вызвать чрезмерную перерисовку.