0

在我的程序中,我使用后台工作线程打开文件。我的程序的主要结构是数据绑定TreeView。在文件读入过程中,动态TreeView节点在从文件TreeView中读入时被添加到。TreeView我提到的这些节点绑定到称为UICollections(继承自的自定义类ObservableCollection<T>)的容器。我创建了这个UICollection<T>类,以确保CollectionViews这种类型永远不会SourceCollections从后台工作线程中更改。我通过更改UICollection被调用的属性IsNotifying来做到这一点false

我的UICollection<T>班级:

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));
    }
}

话虽如此,我在UICollection<T>用我的控制结构实现这一点时遇到了问题,这涉及UICollections从后台工作线程添加。

为清楚起见,我的程序移动如下:

  1. Is a file being opened?: YES -> go into Background worker thread

  2. 在后台工作线程中:(Do we need to create new UICollections?: YES -> go to method in UIThread that does so根据需要迭代)

  3. 关闭线程。

需要理解的主要概念是,如果后台工作线程打开,则UICollection.IsNotifying必须设置为。false对于已经知道的集合,我这样做没有问题,但对于我遇到问题的动态集合。

我的后台工作线程所做的示例:

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...
}

UIThread 中将集合添加到的方法TreeView

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*
}

有了所有这些,将会发生两件事之一......如果我注释掉该行this.IsNotifying = true;,则会出现异常,OnCollectionChanged因为它在后台线程打开时被引发。如果我保持原样,集合将永远不会反映在视图中,因为OnCollectionChanged永远不会被提升,通知视图。我需要做什么才能允许创建这些集合而不会遇到这些错误?我猜问题出在我的AddCollection()函数或UICollection<T>课堂上。

4

1 回答 1

2

如果我理解正确,您正在后台线程上操作一个集合(或嵌套集合),而同一集合(或“父”集合)被用作 UI 中的项目源。即使您禁用更改通知,这也不安全。还有其他一些事情,例如用户发起的排序、扩展树节点、由于虚拟化导致的容器回收等,可能会导致集合被重新查询。如果在另一个线程上更新集合时发生这种情况,则行为未定义。例如,您可以触发一个集合进行迭代,同时另一个线程上的插入导致底层列表被调整大小,可能导致读取空条目或重复条目。每当你在两个线程之间共享可变数据时,你需要同步读取和写入,并且由于您不控制 WPF 内部进行读取,因此您不能假设进行任何类型的并发写入是安全的。这包括从另一个线程修改 UI 绑定集合中的对象。

如果您需要在后台线程上操作集合,拍摄原始集合​​的快照,执行您需要的任何修改,然后将自己编组回 UI 线程以提交更改(通过完全替换原始集合,或清除并重新填充收藏)。我使用这种技术在具有大型数据集的网格视图上安全地执行背景排序、分组和过滤。但如果您这样做,请注意避免修改集合中包含的项目,因为您的 UI 可能仍会引用它们。可能还需要检测 UI 线程上发生的任何可能使您的后台更新无效的更改,在这种情况下,当您将自己编组回 UI 线程时,您需要丢弃您的更改,拍摄另一个快照,

于 2014-11-07T19:14:25.467 回答