从初始化它的线程将项目添加到列表

Add items to a list from the thread that initialized it

我有一个通过 WCF 与服务器通信的 WPF 应用程序。 我在远程服务器上执行一个方法,回调方法在另一个线程上用结果 运行 初始化一个列表。 - 这很好,这正是我申请的目的。

但是当我想向这个列表中添加更多项目时,它会抛出一个异常,我无法从另一个初始化了这个列表的线程中添加项目。

public ObservableCollection<ListBoxItemVM<T>> Items
{
    get { return items; }
    set
    {
        // This section runs on a separate thread.
        items = value;
        notify("Items");
        if (allItems == null)
            allItems = new ObservableCollection<ListBoxItemVM<T>>(items.Clone());

        // I want to save the current context here and use it on the AddItem method

        CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(Items);
        view.Filter = searchFilter;
    }
}

public void AddItem(ListBoxItemVM<T> 
{
    this.items.Add(item); // The following exception throws here
}

Exception: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

我正在寻找一些方法来保存用于初始化列表的线程(或线程的 ExecuteContext),并使用此 thread/context 向该列表添加项目。

需要说明的是,与UI线程无关,我在代码中的另一个区域用UI线程处理了封送处理。 我尝试用 UI SynchronizationContext 编组 this.items.Add(item); 代码,因为它们不同所以失败了。

谢谢

从 .NET 4.5 开始,有一个内置机制可以自动同步对集合的访问并将 CollectionChanged 事件分派到 UI 线程。要启用此功能,您需要在 UI 线程中调用 BindingOperations.EnableCollectionSynchronization

EnableCollectionSynchronization 做了两件事:

记住调用它的线程并使数据绑定管道封送该线程上的 CollectionChanged 事件。 在处理完编组事件之前获取集合的锁,以便事件处理程序 运行 UI 线程不会尝试读取正在从后台线程修改集合的集合。 非常重要的是,这并不能解决所有问题:要确保线程安全地访问本质上不是线程安全的集合,您必须通过在即将修改集合时从后台线程获取相同的锁来与框架合作。

因此正确操作所需的步骤是:

  1. 决定您将使用哪种锁定方式

    这将确定必须使用哪个 EnableCollectionSynchronization 重载。大多数时候一个简单的锁语句就足够了,所以这个重载是标准选择,但如果你使用一些花哨的同步机制,也支持自定义锁。

  2. 创建集合并启用同步

    根据所选的锁定机制,在 UI 线程上调用适当的重载。如果使用标准的锁定语句,您需要提供锁定对象作为参数。如果使用自定义同步,您需要提供一个 CollectionSynchronizationCallback 委托和一个上下文对象(可以为空)。调用时,此委托必须获取您的自定义锁,调用传递给它的 Action 并在返回前释放锁。

  3. 通过在修改之前锁定集合来配合

    当您要自己修改集合时,您也必须使用相同的机制锁定集合;在简单场景中使用传递给 EnableCollectionSynchronization 的同一锁对象上的 lock() 执行此操作,或者在自定义场景中使用相同的自定义同步机制。