<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">最近项目里面发现的一个bug。我们用 ListCollectionView 作为ListBox 的数据源,但是发现在增加和删除 ListCollectionView 中的数据时,它和它的 SourceCollection 数据不同步。 在删除的时候 SourceCollection 已经没有那一项,但是 ListCollectionView 中还是有。 本来以为是 ListCollectionView 的bug, 所以就尝试每次修改数据内容时都 CommitNew(), CommitEdit(), 然后 Refresh() 这个 View。 依然会有这个错误。</span>
然后打开debug exception 后, 发现 ListCollectionView 的SourceCollection 在 CollectionChanged 时间中会抛出异常。 这个异常导致在 SourceCollection 数据内容修改后,view 无法正常更新。因为 view 的更新就是依赖 SourceCollection 的CollectionChanged 时间来更新自己。所以每次这个事件抛出异常, view中的数据就肯定会错。这个异常是说一个已经不在 collection 中的项出发了
CollectionChanged 事件。 我们是用 DynamicObservableCollection 来作为 SourceCollection。 它是我们自己实现的一个集合类。原本认为这个类已经支持多线程。但是实际上,它只是支持 CollectionChanged 的时间处理函数在触发该事件的线程上执行。比如如果是主线程触发了 CollectionChanged 那么事件处理就会在主线程中执行。
这个实现的问题在于,它无法阻止不同线程同时修改这个集合, 甚至同时修改集合中的同一项也是合法的。这就导致不同线程增,减这个集合元素的时候会同时触发 CollectionChanged 事件, 而且不能保证引起这个事件的数据项肯定是存在于这个集合当中的。例如一个线程加入了一个数据项, index = 2, 这时候另外一个线程删除了 index = 2 这一项。然后,加入这一项的线程触发了 SourceCollection 事件,它准备开始执行处理程序, 但是处理程序发现集合中不存在这一项,于是抛出异常。
目前我们的处理是保证对 ListCollectionView 的操作都在主线程中进行。更好的方式是保证 DynamicObservableCollection 的动作会同步,目前还没有实现。
protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e) { NotifyCollectionChangedEventHandler handlers = this.CollectionChanged; if (handlers != null) { foreach (NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList()) { if (handler.Target is CollectionView) ((CollectionView)handler.Target).Refresh(); else handler(this, e); } } }