我在哪里可以得到线程安全的CollectionView?

在后台线程上更新业务对象的集合时,出现以下错误消息:

这种types的CollectionView不支持从与分派器线程不同的线程对其SourceCollection的更改。

好的,这是有道理的。 但是它也提出了一个问题,哪个版本的CollectionView支持multithreading,如何让我的对象使用它?

以下是Jonathan发现的一个改进。 首先,它在与其相关的调度程序上运行每个事件处理程序,而不是假定它们都在同一个(UI)调度程序上。 其次它使用BeginInvoke来允许处理继续,而我们等待分派器变得可用。 这使得解决scheme在后台线程正在进行大量更新并在每个之间进行处理的情况下更快。 也许更重要的是,它克服了在等待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,所以在处理程序被调用之前,被通知的更改可能会被撤消。 这通常会导致“索引超出范围”。 检查事件参数与列表的新(已更改)状态时将引发exception。 为了避免这种情况,所有延迟的事件被replace为重置事件。 这可能会导致在某些情况下过度重绘。

使用:

 System.Windows.Application.Current.Dispatcher.Invoke( System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate() { // Your Action Code }); 

Bea Stollnitz的这篇文章解释了这个错误信息,以及为什么它是这样的。

编辑:从比衣的博客

不幸的是,这段代码导致了一个exception:“NotSupportedException – 这种types的CollectionView不支持从与Dispatcher线程不同的线程修改其SourceCollection。”我明白这个错误信息会导致人们认为,如果CollectionView是使用不支持跨线程更改,那么他们必须find那个。 那么,这个错误信息有点误导:我们提供的CollectionViews都不支持跨线程集合的更改。 不,不幸的是,我们现在还不能解决这个错误信息,我们非常关注。

find一个。

 public class MTObservableCollection<T> : ObservableCollection<T> { public override event NotifyCollectionChangedEventHandler CollectionChanged; protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { var eh = CollectionChanged; if (eh != null) { Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList() let dpo = nh.Target as DispatcherObject where dpo != null select dpo.Dispatcher).FirstOrDefault(); if (dispatcher != null && dispatcher.CheckAccess() == false) { dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e))); } else { foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()) nh.Invoke(this, e); } } } } 

http://www.julmar.com/blog/mark/2009/04/01/AddingToAnObservableCollectionFromABackgroundThread.aspx

你也可以看看: BindingOperations.EnableCollectionSynchronization

请参阅升级到.NET 4.5:ItemsControl与其项目源不一致

对不起,不能添加评论,但这一切都是错误的。

ObservableCollection不是线程安全的。 不仅是因为这个调度问题,而且它根本不是线程安全的(来自msdn):

此types的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的。 任何实例成员不保证是线程安全的。

看这里http://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx

使用“重置”操作调用BeginInvoke时也有一个问题。 “重置”是处理程序应该查看集合本身的唯一操作。 如果您开始调用“重置”,然后立即BeginInvoke一个“添加”的行动比处理程序将接受一个“重置”已经更新的集合和下一个“添加”将创build一个混乱。

这是我的实现工作。 其实我想在所有删除BeginInvoke:

快速执行和线程安全的可观察收集

如果要定期更新WPF UI控件,并同时使用UI,则可以使用DispatcherTimer

XAML

 <Grid> <DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Name="dgDownloads" VerticalAlignment="Top" Width="548" /> <Label Content="" Height="28" HorizontalAlignment="Left" Margin="0,221,0,0" Name="lblFileCouner" VerticalAlignment="Top" Width="173" /> </Grid> 

C#

  public partial class DownloadStats : Window { private MainWindow _parent; DispatcherTimer timer = new DispatcherTimer(); ObservableCollection<FileView> fileViewList = new ObservableCollection<FileView>(); public DownloadStats(MainWindow parent) { InitializeComponent(); _parent = parent; Owner = parent; timer.Interval = new TimeSpan(0, 0, 1); timer.Tick += new EventHandler(timer_Tick); timer.Start(); } void timer_Tick(object sender, EventArgs e) { dgDownloads.ItemsSource = null; fileViewList.Clear(); if (_parent.contentManagerWorkArea.Count > 0) { foreach (var item in _parent.contentManagerWorkArea) { FileView nf = item.Value.FileView; fileViewList.Add(nf); } } if (fileViewList.Count > 0) { lblFileCouner.Content = fileViewList.Count; dgDownloads.ItemsSource = fileViewList; } } } 

没有一个,只需使用Dispatcher.BeginInvoke

尝试这个:

 this.Dispatcher.Invoke(DispatcherPriority.Background, new Action( () => { //Code })); 

以下是一些使用Google和轻微MODS的VB版本。 为我工作。

  Imports System.Collections.ObjectModel Imports System.Collections.Specialized Imports System.ComponentModel Imports System.Reflection Imports System.Windows.Threading 'from: http://stackoverflow.com/questions/2137769/where-do-i-get-a-thread-safe-collectionview Public Class ThreadSafeObservableCollection(Of T) Inherits ObservableCollection(Of T) 'from: http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/listcollectionviewcollectionview-doesnt-support-notifycollectionchanged-with-multiple-items.aspx Protected Overrides Sub OnCollectionChanged(ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs) Dim doit As Boolean = False doit = (e.NewItems IsNot Nothing) AndAlso (e.NewItems.Count > 0) doit = doit OrElse ((e.OldItems IsNot Nothing) AndAlso (e.OldItems.Count > 0)) If (doit) Then Dim handler As NotifyCollectionChangedEventHandler = GetType(ObservableCollection(Of T)).GetField("CollectionChanged", BindingFlags.Instance Or BindingFlags.NonPublic).GetValue(Me) If (handler Is Nothing) Then Return End If For Each invocation As NotifyCollectionChangedEventHandler In handler.GetInvocationList Dim obj As DispatcherObject = invocation.Target If (obj IsNot Nothing) Then Dim disp As Dispatcher = obj.Dispatcher If (disp IsNot Nothing AndAlso Not (disp.CheckAccess())) Then disp.BeginInvoke( Sub() invocation.Invoke(Me, New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)) End Sub, DispatcherPriority.DataBind) Continue For End If End If invocation.Invoke(Me, e) Next End If End Sub End Class 

VB版本中的小错误。 只要更换:

 Dim obj As DispatcherObject = invocation.Target 

通过

 Dim obj As DispatcherObject = TryCast(invocation.Target, DispatcherObject)