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

我正在构建一个应用程序,它使用许多ItemControls(datagrids和listviews)。 为了从后台线程轻松更新这些列表,我将此扩展用于ObservableCollections,它运行良好:

http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx

今天我安装了VS12(后来安装了.NET 4.5),因为我想使用为.NET 4.5编写的组件。 在将我的项目升级到.NET 4.5(从4.0)之前,我的数据网格从workerthread更新时开始抛出InvalidOperationException。 exception消息:

抛出此exception是因为名为’(未命名)’的控件’System.Windows.Controls.DataGrid Items.Count:5’的生成器已收到与Items集合的当前状态不一致的CollectionChanged事件序列。 检测到以下差异:累计计数4与实际计数5不同。[累计计数为(上次重置时计数+ #Adds – 自上次重置后自上次重置)。

Repro代码:

XAML:

     

码:

 public partial class MainWindow : Window { public ExtendedObservableCollection Items { get; private set; } public MainWindow() { InitializeComponent(); Items = new ExtendedObservableCollection(); DataContext = this; Loaded += MainWindow_Loaded; } void MainWindow_Loaded(object sender, RoutedEventArgs e) { Task.Factory.StartNew(() => { foreach (var item in Enumerable.Range(1, 500)) { Items.Add(item); } }); } } 

WPF 4.5提供了一些新function来访问非UI线程上的集合。

它使用WPF可以访问和修改除创建集合之外的线程上的数据集合。 这使您可以使用后台线程从外部源(如数据库)接收数据,并在UI线程上显示数据。 通过使用另一个线程来修改集合,您的用户界面仍然可以响应用户交互。

这可以通过在BindingOperations类上使用静态方法EnableCollectionSynchronization来完成。

如果要收集或修改大量数据,则可能需要使用后台线程来收集和修改数据,以便用户界面对输入保持反应。 要使多个线程能够访问集合,请调用EnableCollectionSynchronization方法。 当您调用EnableCollectionSynchronization(IEnumerable,Object)方法的此重载时,系统会在您访问它时锁定该集合。 要指定自己锁定集合的回调,请调用EnableCollectionSynchronization(IEnumerable,Object,CollectionSynchronizationCallback)重载。

用法如下。 创建一个对象,该对象用作锁的同步集合。 然后调用BindingsOperations的EnableCollectionSynchronization方法,并将要同步的集合和用于锁定的对象传递给它。

我已更新您的代码并添加了详细信息。 此外,我将集合更改为正常的ObservableCollection以避免冲突。

 public partial class MainWindow : Window{ public ObservableCollection Items { get; private set; } //lock object for synchronization; private static object _syncLock = new object(); public MainWindow() { InitializeComponent(); Items = new ObservableCollection(); //Enable the cross acces to this collection elsewhere BindingOperations.EnableCollectionSynchronization(Items, _syncLock); DataContext = this; Loaded += MainWindow_Loaded; } void MainWindow_Loaded(object sender, RoutedEventArgs e) { Task.Factory.StartNew(() => { foreach (var item in Enumerable.Range(1, 500)) { lock(_syncLock) { Items.Add(item); } } }); } } 

另见: http :/ 10/ 10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux

总结此主题,此AsyncObservableCollection适用于.NET 4和.NET 4.5 WPF应用程序。

 using System; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Windows.Data; using System.Windows.Threading; namespace WpfAsyncCollection { public class AsyncObservableCollection : ObservableCollection { public override event NotifyCollectionChangedEventHandler CollectionChanged; private static object _syncLock = new object(); public AsyncObservableCollection() { enableCollectionSynchronization(this, _syncLock); } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { using (BlockReentrancy()) { var eh = CollectionChanged; if (eh == null) return; var 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); } } } private static void enableCollectionSynchronization(IEnumerable collection, object lockObject) { var method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", new Type[] { typeof(IEnumerable), typeof(object) }); if (method != null) { // It's .NET 4.5 method.Invoke(null, new object[] { collection, lockObject }); } } } } 

Jehof的答案是正确的。

我们还不能定位4.5,并且我们的自定义可观察集合已经允许后台更新(在事件通知期间使用Dispatcher)存在此问题。

如果有人发现它有用,我在我们的应用程序中使用了以下代码,它们以.NET 4.0为目标,使其能够在执行环境为.NET 4.5时使用此function:

 public static void EnableCollectionSynchronization(IEnumerable collection, object lockObject) { // Equivalent to .NET 4.5: // BindingOperations.EnableCollectionSynchronization(collection, lockObject); MethodInfo method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", new Type[] { typeof(IEnumerable), typeof(object) }); if (method != null) { method.Invoke(null, new object[] { collection, lockObject }); } } 

这适用于使用可能存在此问题的VS 2017发行版的Windows 10版本1607用户。

 Microsoft Visual Studio Community 2017 Version 15.1 (26403.3) Release VisualStudio.15.Release/15.1.0+26403.3 Microsoft .NET Framework Version 4.6.01586 

您不需要也不需要EnableCollectionSynchronization

          public ObservableCollection fontFamilyItems; public ObservableCollection FontFamilyItems { get { return fontFamilyItems; } set { SetProperty(ref fontFamilyItems, value, nameof(FontFamilyItems)); } } public string fontFamilyItem; public string FontFamilyItem { get { return fontFamilyItem; } set { SetProperty(ref fontFamilyItem, value, nameof(FontFamilyItem)); } } private List GetItems() { List fonts = new List(); foreach (System.Windows.Media.FontFamily font in Fonts.SystemFontFamilies) { fonts.Add(font.Source); .... other stuff.. } return fonts; } public async void OnFontFamilyViewLoaded(object sender, EventArgs e) { DisposableFontFamilyViewLoaded.Dispose(); Task> getItemsTask = Task.Factory.StartNew(GetItems); try { foreach (string item in await getItemsTask) { FontFamilyItems.Add(item); } } catch (Exception x) { throw new Exception("Error - " + x.Message); } ... other stuff }