C# OnCollectionChanged和后台线程的问题
在我的程序中,我使用后台工作线程打开文件。我的程序的主要结构是一个数据绑定的C# OnCollectionChanged和后台线程的问题,c#,wpf,multithreading,observablecollection,inotifycollectionchanged,C#,Wpf,Multithreading,Observablecollection,Inotifycollectionchanged,在我的程序中,我使用后台工作线程打开文件。我的程序的主要结构是一个数据绑定的TreeView。在文件读入过程中,动态TreeView节点在从文件读入时添加到TreeView。我提到的这些TreeView节点绑定到名为UICollections的容器(从observetecollection继承的自定义类)。我创建了UICollection类,以确保此类型的collectionview不会从后台工作线程更改其SourceCollections。为此,我将ui集合中名为IsNotifying的属性更
TreeView
。在文件读入过程中,动态TreeView
节点在从文件读入时添加到TreeView
。我提到的这些TreeView
节点绑定到名为UICollections
的容器(从observetecollection
继承的自定义类)。我创建了UICollection
类,以确保此类型的collectionview
不会从后台工作线程更改其SourceCollections
。为此,我将ui集合中名为IsNotifying
的属性更改为false
MyUICollection
class:
public class UICollection<T> : ObservableCollection<T>
{
public UICollection()
{
IsNotifying = true;
}
public UICollection(IEnumerable<T> source)
{
this.Load(source);
}
public bool IsNotifying { get; set; }
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (IsNotifying)
base.OnPropertyChanged(e);
}
//Does not raise unless IsNotifying = true
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (IsNotifying)
base.OnCollectionChanged(e);
}
//Used whenever I re-order a collection
public virtual void Load(IEnumerable<T> items)
{
if (items == null)
throw new ArgumentNullException("items");
this.IsNotifying = false;
foreach (var item in items)
this.Add(item);
//ERROR created on this line because IsNotifying is always set to true
this.IsNotifying = true;
this.Refresh();
}
public Action<T> OnSelectedItemChanged { get; set; }
public Func<T, bool> GetDefaultItem { get; set; }
public void Refresh()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private void openFile()
{
//Turn off CollectionChanged for known Collections
KnownCollections1.IsNotifying = false;
KnownCollections2.IsNotifying = false; //... and so on
//Do we need to create new collections? YES -> Call to AddCollection in UIThread
//Refresh known collections
App.Current.Dispatcher.BeginInvoke((Action)(() => KnownCollections1.Refresh()));
App.Current.Dispatcher.BeginInvoke((Action)(() => KnownCollections2.Refresh())); //... and so on
//If new collections exist find them and refresh them...
}
public void AddCollection(string DisplayName, int locationValue)
{
node.Children.Add(CreateLocationNode(displayName, locationValue)); //Add to parent node
for (int i = 0; i < node.Children.Count(); i++)
{
//make sure IsNotifying = false for newly added collection
if (node.Children[i].locationValue == locationValue)
node.Children[i].Children.IsNotifying = false;
}
//Order the collection based on numerical value
var ordered = node.Children.OrderBy(n => n.TreeView_LocValue).ToList();
node.Children.Clear();
node.Children.Load(ordered); //Pass to UICollection class -- *RUNS INTO ERROR*
}
UIThread中的方法,该方法将集合添加到TreeView
:
public class UICollection<T> : ObservableCollection<T>
{
public UICollection()
{
IsNotifying = true;
}
public UICollection(IEnumerable<T> source)
{
this.Load(source);
}
public bool IsNotifying { get; set; }
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (IsNotifying)
base.OnPropertyChanged(e);
}
//Does not raise unless IsNotifying = true
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (IsNotifying)
base.OnCollectionChanged(e);
}
//Used whenever I re-order a collection
public virtual void Load(IEnumerable<T> items)
{
if (items == null)
throw new ArgumentNullException("items");
this.IsNotifying = false;
foreach (var item in items)
this.Add(item);
//ERROR created on this line because IsNotifying is always set to true
this.IsNotifying = true;
this.Refresh();
}
public Action<T> OnSelectedItemChanged { get; set; }
public Func<T, bool> GetDefaultItem { get; set; }
public void Refresh()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private void openFile()
{
//Turn off CollectionChanged for known Collections
KnownCollections1.IsNotifying = false;
KnownCollections2.IsNotifying = false; //... and so on
//Do we need to create new collections? YES -> Call to AddCollection in UIThread
//Refresh known collections
App.Current.Dispatcher.BeginInvoke((Action)(() => KnownCollections1.Refresh()));
App.Current.Dispatcher.BeginInvoke((Action)(() => KnownCollections2.Refresh())); //... and so on
//If new collections exist find them and refresh them...
}
public void AddCollection(string DisplayName, int locationValue)
{
node.Children.Add(CreateLocationNode(displayName, locationValue)); //Add to parent node
for (int i = 0; i < node.Children.Count(); i++)
{
//make sure IsNotifying = false for newly added collection
if (node.Children[i].locationValue == locationValue)
node.Children[i].Children.IsNotifying = false;
}
//Order the collection based on numerical value
var ordered = node.Children.OrderBy(n => n.TreeView_LocValue).ToList();
node.Children.Clear();
node.Children.Load(ordered); //Pass to UICollection class -- *RUNS INTO ERROR*
}
public void AddCollection(字符串DisplayName,int locationValue)
{
node.Children.Add(CreateLocationNode(displayName,locationValue));//添加到父节点
for(int i=0;in.TreeView_LocValue).ToList();
node.Children.Clear();
node.Children.Load(有序);//传递到UICollection类--*时出错*
}
有了这些,两件事中的一件会发生。。。如果我注释掉行this.IsNotifying=true
,则CollectionChanged中将出现异常,因为它是在backround线程打开时引发的。如果我将行保持原样,集合将永远不会反映在视图中,因为OnCollectionChanged
永远不会被引发,从而通知视图。我需要做些什么才能允许创建这些集合而不会遇到这些错误?我猜问题可能出在我的AddCollection()
函数中,或者在UICollection
类中。如果我理解正确,您正在后台线程上操作一个集合(或嵌套集合),而同一个集合(或“父”集合)正在UI中用作项源。这是不安全的,即使您禁用了更改通知。还有其他因素,如用户发起的排序、扩展树节点、虚拟化导致的容器回收等,可能会导致重新查询集合。如果在另一个线程上更新集合时发生这种情况,则行为未定义。例如,您可以在另一个线程上的插入导致调整基础列表的大小的同时触发对集合进行迭代,这可能会导致读取空项或重复项。当您在两个线程之间共享可变数据时,您需要同步读写,并且由于您不控制WPF内部进行读写,因此您不能假设执行任何类型的并发写是安全的。它包括从另一个线程修改UI绑定集合中的对象
如果需要在后台线程上操作集合,请拍摄原始集合的快照,执行所需的任何修改,然后将自己封送回UI线程以提交更改(或者完全替换原始集合,或者清除并重新填充集合)。我使用这种技术安全地对具有大数据集的网格视图执行背景排序、分组和过滤。但是,如果这样做,请小心避免修改集合中包含的项,因为它们可能仍然被UI引用。可能还需要检测UI线程上发生的任何更改,这些更改可能会使后台更新无效,在这种情况下,当您将自己封送回UI线程时,您需要放弃更改,拍摄另一个快照,然后重新开始(或者想出一种更精细的方法来协调这两组更改).你好,迈克。听起来你很理解我的帖子。我知道时间很长,但我一直在尽可能地说清楚。您是否建议我在移动到UIThread以更改集合时冻结背景线程?我是说,如果您的UI线程引用了背景线程上的任何集合,则您不能修改它。通常,您所能做的最好的事情是复制原始集合,对副本进行背景修改,完成后,将自己封送回UI线程,用修改后的副本替换原始集合。如果您最终在UI线程和后台线程上都进行了修改,那么事情会变得更加复杂,您需要找到一种方法来协调这两组更改,但这也需要在UI线程上进行。谢谢@MikeStrobel。将需要添加的所有集合排队并在后台工作线程关闭后添加它们是否更容易?@Ericafterdark可能是。如果这些集合是您正在后台线程上填充的全新添加,那么是的,将它们封送回UI线程以将它们插入到树中可能是有利的,可以一次全部插入,也可以在它们完全填充时插入。重要的是,在后台线程完全填充/修改并“忘记”集合之前,UI线程无法读取集合。